diff --git a/.travis.yml b/.travis.yml index ab3b7be5ce..384c093c5c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,9 +7,9 @@ cache: jobs: include: - # 1.1 Semver check + # 1.1 Semver check disabled for v5. We're breaking it. # - stage: test -# jdk: openjdk11 +# jdk: openjdk8 # script: mvn verify -Pcheck-semantic-version -DskipTests=true # env: CHECK_SEMANTIC_VERSION=true diff --git a/cdi2/pom.xml b/cdi2/pom.xml index 4ed887f167..0cd9e81d71 100644 --- a/cdi2/pom.xml +++ b/cdi2/pom.xml @@ -8,7 +8,7 @@ io.cucumber cucumber-jvm - 4.7.2-SNAPSHOT + 5.0.0-SNAPSHOT cucumber-cdi2 diff --git a/cdi2/src/test/java/io/cucumber/cdi2/BellyStepdefs.java b/cdi2/src/test/java/io/cucumber/cdi2/BellyStepdefs.java index 2655750cd9..30ab970d44 100644 --- a/cdi2/src/test/java/io/cucumber/cdi2/BellyStepdefs.java +++ b/cdi2/src/test/java/io/cucumber/cdi2/BellyStepdefs.java @@ -1,6 +1,6 @@ package io.cucumber.cdi2; -import cucumber.api.java.en.Given; +import io.cucumber.java.en.Given; import javax.enterprise.inject.Vetoed; import javax.inject.Inject; diff --git a/cdi2/src/test/java/io/cucumber/cdi2/CDIBellyStepdefs.java b/cdi2/src/test/java/io/cucumber/cdi2/CDIBellyStepdefs.java index 8609664829..e873e368b0 100644 --- a/cdi2/src/test/java/io/cucumber/cdi2/CDIBellyStepdefs.java +++ b/cdi2/src/test/java/io/cucumber/cdi2/CDIBellyStepdefs.java @@ -1,6 +1,6 @@ package io.cucumber.cdi2; -import cucumber.api.java.en.Then; +import io.cucumber.java.en.Then; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; diff --git a/cdi2/src/test/java/io/cucumber/cdi2/UnusedGlue.java b/cdi2/src/test/java/io/cucumber/cdi2/UnusedGlue.java index 456f46b66d..9434eb94e8 100644 --- a/cdi2/src/test/java/io/cucumber/cdi2/UnusedGlue.java +++ b/cdi2/src/test/java/io/cucumber/cdi2/UnusedGlue.java @@ -1,7 +1,7 @@ package io.cucumber.cdi2; -import cucumber.api.java.Before; -import cucumber.api.java.en.Given; +import io.cucumber.java.Before; +import io.cucumber.java.en.Given; public class UnusedGlue { public UnusedGlue() { diff --git a/core/pom.xml b/core/pom.xml index b2d2cae79d..f232c4da65 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 4.7.2-SNAPSHOT + 5.0.0-SNAPSHOT cucumber-core @@ -19,6 +19,7 @@ 0.4.15 0.3.0 0.2 + io.cucumber.core @@ -42,11 +43,6 @@ org.apiguardian apiguardian-api - - junit - junit - test - xmlunit xmlunit @@ -64,6 +60,19 @@ ${jsoup.version} test + + + org.junit.jupiter + junit-jupiter-api + test + + + + org.junit.vintage + junit-vintage-engine + test + + org.webbitserver webbit @@ -77,8 +86,6 @@ test - - uk.co.datumedge hamcrest-json @@ -113,7 +120,7 @@ - cucumber.api.cli.Main + io.cucumber.core.api.cli.Main @@ -127,82 +134,7 @@ - - org.revapi - revapi-maven-plugin - - - - - java.method.addedToInterface - method void cucumber.api.TypeRegistry::setDefaultParameterTransformer(io.cucumber.cucumberexpressions.ParameterByTypeTransformer) - cucumber.api - cucumber.api.TypeRegistry - TypeRegistry - setDefaultParameterTransformer - method - This interface can be used by users of Cucumber but should not be implemented. - - - java.method.numberOfParametersChanged - method java.util.List<io.cucumber.stepexpression.Argument> io.cucumber.stepexpression.ArgumentMatcher::argumentsFrom(gherkin.pickles.PickleStep) - method java.util.List<io.cucumber.stepexpression.Argument> io.cucumber.stepexpression.ArgumentMatcher::argumentsFrom(gherkin.pickles.PickleStep, java.lang.reflect.Type[]) - io.cucumber.stepexpression - io.cucumber.stepexpression.ArgumentMatcher - ArgumentMatcher - argumentsFrom - method - This class is part of Cucumbers internal API. The change adds a vararg argument. - - - java.method.numberOfParametersChanged - method java.util.List<io.cucumber.stepexpression.Argument> io.cucumber.stepexpression.ExpressionArgumentMatcher::argumentsFrom(gherkin.pickles.PickleStep) - method java.util.List<io.cucumber.stepexpression.Argument> io.cucumber.stepexpression.ExpressionArgumentMatcher::argumentsFrom(gherkin.pickles.PickleStep, java.lang.reflect.Type[]) - io.cucumber.stepexpression - io.cucumber.stepexpression.ExpressionArgumentMatcher - ExpressionArgumentMatcher - argumentsFrom - method - This class is part of Cucumbers internal API. - - - java.method.numberOfParametersChanged - method java.util.List<io.cucumber.stepexpression.Argument> io.cucumber.stepexpression.StepExpression::match(java.lang.String) - method java.util.List<io.cucumber.stepexpression.Argument> io.cucumber.stepexpression.StepExpression::match(java.lang.String, java.lang.reflect.Type[]) - io.cucumber.stepexpression - io.cucumber.stepexpression.StepExpression - StepExpression - match - method - This class is part of Cucumbers internal API. The change adds a vararg argument. - - - java.method.numberOfParametersChanged - method java.util.List<io.cucumber.stepexpression.Argument> io.cucumber.stepexpression.StepExpression::match(java.lang.String, java.lang.String) - method java.util.List<io.cucumber.stepexpression.Argument> io.cucumber.stepexpression.StepExpression::match(java.lang.String, java.lang.String, java.lang.reflect.Type[]) - io.cucumber.stepexpression - io.cucumber.stepexpression.StepExpression - StepExpression - match - method - This class is part of Cucumbers internal API. The change adds a vararg argument. - - - java.method.numberOfParametersChanged - method java.util.List<io.cucumber.stepexpression.Argument> io.cucumber.stepexpression.StepExpression::match(java.lang.String, java.util.List<java.util.List<java.lang.String>>) - method java.util.List<io.cucumber.stepexpression.Argument> io.cucumber.stepexpression.StepExpression::match(java.lang.String, java.util.List<java.util.List<java.lang.String>>, java.lang.reflect.Type[]) - io.cucumber.stepexpression - io.cucumber.stepexpression.StepExpression - StepExpression - match - method - This class is part of Cucumbers internal API. The change adds a vararg argument. - - - - - - + diff --git a/core/src/main/java/cucumber/api/HookType.java b/core/src/main/java/cucumber/api/HookType.java deleted file mode 100644 index 5d1d2a32c3..0000000000 --- a/core/src/main/java/cucumber/api/HookType.java +++ /dev/null @@ -1,12 +0,0 @@ -package cucumber.api; - -import static java.util.Locale.ROOT; - -public enum HookType { - Before, After, BeforeStep, AfterStep; - - @Override - public String toString() { - return super.toString().toLowerCase(ROOT); - } -} diff --git a/core/src/main/java/cucumber/api/PendingException.java b/core/src/main/java/cucumber/api/PendingException.java deleted file mode 100644 index be30dd1c65..0000000000 --- a/core/src/main/java/cucumber/api/PendingException.java +++ /dev/null @@ -1,13 +0,0 @@ -package cucumber.api; - -// We're deliberately not extending CucumberException (which is used to signal fatal errors) -@Pending -public class PendingException extends RuntimeException { - public PendingException() { - this("TODO: implement me"); - } - - public PendingException(String message) { - super(message); - } -} diff --git a/core/src/main/java/cucumber/api/Result.java b/core/src/main/java/cucumber/api/Result.java deleted file mode 100644 index e311cb2883..0000000000 --- a/core/src/main/java/cucumber/api/Result.java +++ /dev/null @@ -1,124 +0,0 @@ -package cucumber.api; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.Comparator; -import java.util.Objects; - -import static java.util.Locale.ROOT; -import static java.util.Objects.requireNonNull; - -public final class Result { - - public static final Comparator SEVERITY = new Comparator() { - - @Override - public int compare(Result a, Result b) { - return a.status == b.status ? 0 : a.status.ordinal() > b.status.ordinal() ? 1 : -1; - } - }; - - private final Type status; - private final Long duration; - private final Throwable error; - public static final Result UNDEFINED = new Result(Result.Type.UNDEFINED, 0L, null); - public enum Type { - PASSED, - SKIPPED, - PENDING, - UNDEFINED, - AMBIGUOUS, - FAILED, - UNUSED; - - public static Type fromLowerCaseName(String lowerCaseName) { - return valueOf(lowerCaseName.toUpperCase(ROOT)); - } - - public String lowerCaseName() { - return name().toLowerCase(ROOT); - } - - public String firstLetterCapitalizedName() { - return name().substring(0, 1) + name().substring(1).toLowerCase(ROOT); - } - - - } - - /** - * The result of a step or scenario - * - * @param status status of the step or scenario - * @param duration the duration in nanoseconds - * @param error the error that caused the failure if any - */ - public Result(Result.Type status, Long duration, Throwable error) { - this.status = requireNonNull(status); - this.duration = requireNonNull(duration); - this.error = error; - } - - public Result.Type getStatus() { - return status; - } - - public Long getDuration() { - return duration; - } - - public String getErrorMessage() { - return error != null ? getErrorMessage(error) : null; - } - - public Throwable getError() { - return error; - } - - public boolean is(Result.Type status) { - return this.status == status; - } - - public boolean isOk(boolean isStrict) { - return hasAlwaysOkStatus() || !isStrict && hasOkWhenNotStrictStatus(); - } - - private boolean hasAlwaysOkStatus() { - return is(Result.Type.PASSED) || is(Result.Type.SKIPPED); - } - - private boolean hasOkWhenNotStrictStatus() { - return is(Result.Type.UNDEFINED) || is(Result.Type.PENDING); - } - - private String getErrorMessage(Throwable error) { - StringWriter stringWriter = new StringWriter(); - PrintWriter printWriter = new PrintWriter(stringWriter); - error.printStackTrace(printWriter); - return stringWriter.getBuffer().toString(); - } - - @Override - public String toString() { - return "Result{" + - "status=" + status + - ", duration=" + duration + - ", error=" + error + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Result result = (Result) o; - return status == result.status && - Objects.equals(duration, result.duration) && - Objects.equals(error, result.error); - } - - @Override - public int hashCode() { - return Objects.hash(status, duration, error); - } -} diff --git a/core/src/main/java/cucumber/api/Scenario.java b/core/src/main/java/cucumber/api/Scenario.java deleted file mode 100644 index 22a783c8b1..0000000000 --- a/core/src/main/java/cucumber/api/Scenario.java +++ /dev/null @@ -1,83 +0,0 @@ -package cucumber.api; - -import java.util.Collection; -import java.util.List; - -/** - * Before or After Hooks that declare a parameter of this type will receive an instance of this class. - * It allows writing text and embedding media into reports, as well as inspecting results (in an After block). - * - * @deprecated use {@link io.cucumber.core.api.Scenario} instead. - */ -@Deprecated -public interface Scenario { - /** - * @return source_tag_names. Needed for compatibility with Capybara. - */ - Collection getSourceTagNames(); - - /** - * @return the most severe status of the Scenario's Steps. - */ - Result.Type getStatus(); - - /** - * @return true if and only if {@link #getStatus()} returns "failed" - */ - boolean isFailed(); - - /** - * Embeds data into the report(s). Some reporters (such as the progress one) don't embed data, but others do (html and json). - * Example: - * - *
-     * {@code
-     * // Embed a screenshot. See your UI automation tool's docs for
-     * // details about how to take a screenshot.
-     * scenario.embed(pngBytes, "image/png");
-     * }
-     * 
- * - * @param data what to embed, for example an image. - * @param mimeType what is the data? - */ - void embed(byte[] data, String mimeType); - - /** - * Like {@link Scenario#embed(byte[], String)}, but with name for the embedding. - * - * @param data what to embed, for example an image. - * @param mimeType what is the data? - * @param name embedding name - */ - void embed(byte[] data, String mimeType, String name); - - /** - * Outputs some text into the report. - * - * @param text what to put in the report. - */ - void write(String text); - - /** - * - * @return the name of the Scenario - */ - String getName(); - - /** - * @return the id of the Scenario. - */ - String getId(); - - /** - * @return the uri of the Scenario. - */ - String getUri(); - - /** - * @return the line(s) in the feature file of the Scenario. Scenarios from Scenario Outlines - * return both the line of the example row the the line of the scenario outline. - */ - List getLines(); -} diff --git a/core/src/main/java/cucumber/api/SnippetType.java b/core/src/main/java/cucumber/api/SnippetType.java deleted file mode 100644 index 54c6217450..0000000000 --- a/core/src/main/java/cucumber/api/SnippetType.java +++ /dev/null @@ -1,37 +0,0 @@ -package cucumber.api; - -import cucumber.runtime.CucumberException; -import cucumber.runtime.snippets.CamelCaseConcatenator; -import cucumber.runtime.snippets.Concatenator; -import cucumber.runtime.snippets.FunctionNameGenerator; -import cucumber.runtime.snippets.UnderscoreConcatenator; - -/** - * @deprecated use {@link io.cucumber.junit.CucumberOptions} or {@link io.cucumber.testng.CucumberOptions} instead. - */ -@Deprecated -public enum SnippetType { - UNDERSCORE("underscore", new UnderscoreConcatenator()), - CAMELCASE("camelcase", new CamelCaseConcatenator()); - - private final String name; - private final Concatenator concatenator; - - SnippetType(String name, Concatenator concatenator) { - this.name = name; - this.concatenator = concatenator; - } - - public static SnippetType fromString(String name) { - for (SnippetType snippetType : SnippetType.values()) { - if (name.equalsIgnoreCase(snippetType.name)) { - return snippetType; - } - } - throw new CucumberException(String.format("Unrecognized SnippetType %s", name)); - } - - public FunctionNameGenerator getFunctionNameGenerator() { - return new FunctionNameGenerator(concatenator); - } -} diff --git a/core/src/main/java/cucumber/api/StepDefinitionReporter.java b/core/src/main/java/cucumber/api/StepDefinitionReporter.java deleted file mode 100644 index 6a901eedb8..0000000000 --- a/core/src/main/java/cucumber/api/StepDefinitionReporter.java +++ /dev/null @@ -1,17 +0,0 @@ -package cucumber.api; - -import cucumber.api.event.StepDefinedEvent; -import cucumber.runtime.StepDefinition; - -/** - * @deprecated in favor of {@link StepDefinedEvent}, as Lambda Step Definitions are not reported through this class. - */ -@Deprecated -public interface StepDefinitionReporter extends Plugin { - /** - * Called when a step definition is defined - * - * @param stepDefinition the step definition - */ - void stepDefinition(StepDefinition stepDefinition); -} diff --git a/core/src/main/java/cucumber/api/TestCase.java b/core/src/main/java/cucumber/api/TestCase.java deleted file mode 100644 index 90fcce0bcb..0000000000 --- a/core/src/main/java/cucumber/api/TestCase.java +++ /dev/null @@ -1,19 +0,0 @@ -package cucumber.api; - -import gherkin.pickles.PickleTag; - -import java.util.List; - -public interface TestCase { - int getLine(); - - String getName(); - - String getScenarioDesignation(); - - List getTags(); - - List getTestSteps(); - - String getUri(); -} diff --git a/core/src/main/java/cucumber/api/Transpose.java b/core/src/main/java/cucumber/api/Transpose.java deleted file mode 100644 index db61dfb931..0000000000 --- a/core/src/main/java/cucumber/api/Transpose.java +++ /dev/null @@ -1,52 +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 - * to transpose a DataTable. - *

- * For example, if you have the following Gherkin step with a table - *

- * Given the user is
- *    | firstname	| Roberto	|
- *    | lastname	| Lo Giacco |
- *    | nationality	| Italian	|
- * 
- *

- * And a data table type to create a User - * - *

- * typeRegistry.defineDataTableType(new DataTableType(
- *    Author.class,
- *    new TableEntryTransformer<User>() {
- *    @Override
- *    public Author transform(Map<String, String> entry) {
- *       return new User(
- *          entry.get("firstName"),
- *          entry.get("lastName"),
- *          entry.get("nationality"));
- *    }
- * }));
- *
- * 
- * Then the following Java Step Definition would convert that into an User object: - *
- * @Given("^the user is$")
- * public void the_user_is(@Transpose User user) {
- *     this.user = user;
- * }
- * 
- * - * @deprecated use {@link io.cucumber.java.Transpose} instead. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.PARAMETER}) -@Deprecated -public @interface Transpose { - boolean value() default true; -} diff --git a/core/src/main/java/cucumber/api/TypeRegistry.java b/core/src/main/java/cucumber/api/TypeRegistry.java deleted file mode 100644 index 7641e9b162..0000000000 --- a/core/src/main/java/cucumber/api/TypeRegistry.java +++ /dev/null @@ -1,42 +0,0 @@ -package cucumber.api; - -import io.cucumber.cucumberexpressions.ParameterByTypeTransformer; -import io.cucumber.cucumberexpressions.ParameterType; -import io.cucumber.datatable.DataTableType; -import io.cucumber.datatable.TableCellByTypeTransformer; -import io.cucumber.datatable.TableEntryByTypeTransformer; - -/** - * @deprecated use {@link io.cucumber.core.api.TypeRegistry} instead. - */ -@Deprecated -public interface TypeRegistry{ - - void defineParameterType(ParameterType parameterType); - - void defineDataTableType(DataTableType tableType); - - /** - * Set default transformer for parameters which are not defined by - * {@code defineParameterType(ParameterType))} - * - * @param defaultParameterByTypeTransformer default transformer - */ - void setDefaultParameterTransformer(ParameterByTypeTransformer defaultParameterByTypeTransformer); - - /** - * Set default transformer for entries which are not defined by - * {@code defineDataTableType(new DataTableType(Class,TableEntryTransformer))} - * - * @param tableEntryByTypeTransformer default transformer - */ - void setDefaultDataTableEntryTransformer(TableEntryByTypeTransformer tableEntryByTypeTransformer); - - /** - * Set default transformer for cells which are not defined by - * {@code defineDataTableType(new DataTableType(Class,TableEntryTransformer))} - * - * @param tableCellByTypeTransformer default transformer - */ - void setDefaultDataTableCellTransformer(TableCellByTypeTransformer tableCellByTypeTransformer); -} diff --git a/core/src/main/java/cucumber/api/TypeRegistryConfigurer.java b/core/src/main/java/cucumber/api/TypeRegistryConfigurer.java deleted file mode 100644 index b311e3b5ad..0000000000 --- a/core/src/main/java/cucumber/api/TypeRegistryConfigurer.java +++ /dev/null @@ -1,14 +0,0 @@ -package cucumber.api; - -import java.util.Locale; - -/** - * @deprecated use {@link io.cucumber.core.api.TypeRegistryConfigurer} instead. - */ -@Deprecated -public interface TypeRegistryConfigurer { - - Locale locale(); - - void configureTypeRegistry(TypeRegistry typeRegistry); -} diff --git a/core/src/main/java/cucumber/api/cli/Main.java b/core/src/main/java/cucumber/api/cli/Main.java index 71ef75f0d5..58f37f97ce 100644 --- a/core/src/main/java/cucumber/api/cli/Main.java +++ b/core/src/main/java/cucumber/api/cli/Main.java @@ -27,4 +27,4 @@ public static byte run(String[] argv, ClassLoader classLoader) { log.warn("You are using deprecated Main class. Please use io.cucumber.core.cli.Main"); return io.cucumber.core.cli.Main.run(argv, classLoader); } -} +} \ No newline at end of file diff --git a/core/src/main/java/cucumber/api/event/EmbedEvent.java b/core/src/main/java/cucumber/api/event/EmbedEvent.java deleted file mode 100644 index ce4c698cb4..0000000000 --- a/core/src/main/java/cucumber/api/event/EmbedEvent.java +++ /dev/null @@ -1,29 +0,0 @@ -package cucumber.api.event; - -import cucumber.api.TestCase; - -public final class EmbedEvent extends TestCaseEvent { - public final byte[] data; - public final String mimeType; - public final String name; - - @Deprecated - public EmbedEvent(Long timeStamp, TestCase testCase, byte[] data, String mimeType) { - this(timeStamp, 0, testCase, data, mimeType); - } - - public EmbedEvent(Long timeStamp, long timeStampMillis, TestCase testCase, byte[] data, String mimeType) { - super(timeStamp, timeStampMillis, testCase); - this.data = data; - this.mimeType = mimeType; - this.name = null; - } - - public EmbedEvent(Long timeStamp, long timeStampMillis, TestCase testCase, byte[] data, String mimeType, String name) { - super(timeStamp, timeStampMillis, testCase); - this.data = data; - this.mimeType = mimeType; - this.name = name; - } - -} diff --git a/core/src/main/java/cucumber/api/event/Event.java b/core/src/main/java/cucumber/api/event/Event.java deleted file mode 100644 index 06976a9f9b..0000000000 --- a/core/src/main/java/cucumber/api/event/Event.java +++ /dev/null @@ -1,43 +0,0 @@ -package cucumber.api.event; - -import java.util.Comparator; - -public interface Event { - - /** - * When pickles are executed in parallel events can be - * produced with a partial ordering. - *

- * The canonical order is the order in which these events - * would have been generated had cucumber executed these - * pickles in a serial fashion. - *

- * In canonical order events are first ordered by type: - *

    - *
  1. TestRunStarted - *
  2. TestSourceRead - *
  3. SnippetsSuggestedEvent - *
  4. TestCaseEvent - *
  5. TestRunFinished - *
- *

- * Then TestCaseEvents are ordered by - *

    - *
  1. uri - *
  2. line - *
  3. timestamp - *
- */ - Comparator CANONICAL_ORDER = new CanonicalEventOrder(); - - /** - * Returns timestamp in nano seconds since an arbitrary start time. - * - * @return timestamp in nano seconds - * @see System#nanoTime() - * @deprecated prefer {@link TimeStampedEvent#getTimeStampMillis()} - */ - @Deprecated - Long getTimeStamp(); - -} diff --git a/core/src/main/java/cucumber/api/event/EventHandler.java b/core/src/main/java/cucumber/api/event/EventHandler.java deleted file mode 100644 index ed4d2ff334..0000000000 --- a/core/src/main/java/cucumber/api/event/EventHandler.java +++ /dev/null @@ -1,7 +0,0 @@ -package cucumber.api.event; - -public interface EventHandler { - - void receive(T event); - -} diff --git a/core/src/main/java/cucumber/api/event/SnippetsSuggestedEvent.java b/core/src/main/java/cucumber/api/event/SnippetsSuggestedEvent.java deleted file mode 100644 index af048dac55..0000000000 --- a/core/src/main/java/cucumber/api/event/SnippetsSuggestedEvent.java +++ /dev/null @@ -1,25 +0,0 @@ -package cucumber.api.event; - -import gherkin.pickles.PickleLocation; - -import java.util.Collections; -import java.util.List; - -public class SnippetsSuggestedEvent extends TimeStampedEvent { - public final String uri; - public final List stepLocations; - public final List snippets; - - @Deprecated - public SnippetsSuggestedEvent(Long timeStamp, String uri, List stepLocations, List snippets) { - this(timeStamp, 0, uri, stepLocations, snippets); - } - - public SnippetsSuggestedEvent(Long timeStamp, long timeStampMillis, String uri, List stepLocations, List snippets) { - super(timeStamp, timeStampMillis); - this.uri = uri; - this.stepLocations = stepLocations; - this.snippets = Collections.unmodifiableList(snippets); - } - -} diff --git a/core/src/main/java/cucumber/api/event/StepDefinedEvent.java b/core/src/main/java/cucumber/api/event/StepDefinedEvent.java deleted file mode 100644 index 245f68eff4..0000000000 --- a/core/src/main/java/cucumber/api/event/StepDefinedEvent.java +++ /dev/null @@ -1,13 +0,0 @@ -package cucumber.api.event; - -import cucumber.runtime.StepDefinition; - -public class StepDefinedEvent extends TimeStampedEvent { - public final StepDefinition stepDefinition; - - public StepDefinedEvent(Long time, Long timeMillis, StepDefinition stepDefinition) { - super(time, timeMillis); - this.stepDefinition = stepDefinition; - } - -} diff --git a/core/src/main/java/cucumber/api/event/TestCaseEvent.java b/core/src/main/java/cucumber/api/event/TestCaseEvent.java deleted file mode 100644 index 33815b77a1..0000000000 --- a/core/src/main/java/cucumber/api/event/TestCaseEvent.java +++ /dev/null @@ -1,17 +0,0 @@ -package cucumber.api.event; - -import cucumber.api.TestCase; - -public abstract class TestCaseEvent extends TimeStampedEvent { - - private final TestCase testCase; - - TestCaseEvent(Long timeStamp, long timeStampMillis, TestCase testCase) { - super(timeStamp, timeStampMillis); - this.testCase = testCase; - } - - public TestCase getTestCase() { - return testCase; - } -} diff --git a/core/src/main/java/cucumber/api/event/TestCaseFinished.java b/core/src/main/java/cucumber/api/event/TestCaseFinished.java deleted file mode 100644 index 967dafbc17..0000000000 --- a/core/src/main/java/cucumber/api/event/TestCaseFinished.java +++ /dev/null @@ -1,21 +0,0 @@ -package cucumber.api.event; - -import cucumber.api.Result; -import cucumber.api.TestCase; - -public final class TestCaseFinished extends TestCaseEvent { - public final Result result; - public final TestCase testCase; - - @Deprecated - public TestCaseFinished(Long timeStamp, TestCase testCase, Result result) { - this(timeStamp, 0, testCase, result); - } - - public TestCaseFinished(Long timeStamp, long timeStampMillis, TestCase testCase, Result result) { - super(timeStamp, timeStampMillis, testCase); - this.testCase = testCase; - this.result = result; - } - -} diff --git a/core/src/main/java/cucumber/api/event/TestCaseStarted.java b/core/src/main/java/cucumber/api/event/TestCaseStarted.java deleted file mode 100644 index 5fb4d3da66..0000000000 --- a/core/src/main/java/cucumber/api/event/TestCaseStarted.java +++ /dev/null @@ -1,18 +0,0 @@ -package cucumber.api.event; - -import cucumber.api.TestCase; - -public final class TestCaseStarted extends TestCaseEvent { - public final TestCase testCase; - - @Deprecated - public TestCaseStarted(Long timeStamp, TestCase testCase) { - this(timeStamp, 0L, testCase); - } - - public TestCaseStarted(Long timeStamp, long timeStampMillis, TestCase testCase) { - super(timeStamp, timeStampMillis, testCase); - this.testCase = testCase; - } - -} diff --git a/core/src/main/java/cucumber/api/event/TestRunFinished.java b/core/src/main/java/cucumber/api/event/TestRunFinished.java deleted file mode 100644 index 42556775e2..0000000000 --- a/core/src/main/java/cucumber/api/event/TestRunFinished.java +++ /dev/null @@ -1,13 +0,0 @@ -package cucumber.api.event; - -public final class TestRunFinished extends TimeStampedEvent { - - @Deprecated - public TestRunFinished(Long timeStamp) { - this(timeStamp, 0); - } - - public TestRunFinished(Long timeStamp, long timeStampMillis) { - super(timeStamp, timeStampMillis); - } -} diff --git a/core/src/main/java/cucumber/api/event/TestRunStarted.java b/core/src/main/java/cucumber/api/event/TestRunStarted.java deleted file mode 100644 index 93f02f977c..0000000000 --- a/core/src/main/java/cucumber/api/event/TestRunStarted.java +++ /dev/null @@ -1,13 +0,0 @@ -package cucumber.api.event; - -public final class TestRunStarted extends TimeStampedEvent { - - @Deprecated - public TestRunStarted(Long timeStamp) { - this(timeStamp, 0); - } - - public TestRunStarted(Long timeStamp, long timeStampMillis) { - super(timeStamp, timeStampMillis); - } -} diff --git a/core/src/main/java/cucumber/api/event/TestSourceRead.java b/core/src/main/java/cucumber/api/event/TestSourceRead.java deleted file mode 100644 index 822e851add..0000000000 --- a/core/src/main/java/cucumber/api/event/TestSourceRead.java +++ /dev/null @@ -1,18 +0,0 @@ -package cucumber.api.event; - -public final class TestSourceRead extends TimeStampedEvent { - public final String uri; - public final String source; - - @Deprecated - public TestSourceRead(Long timeStamp, String uri, String source) { - this(timeStamp, 0, uri, source); - } - - public TestSourceRead(Long timeStamp, long timeStampMillis, String uri, String source) { - super(timeStamp, timeStampMillis); - this.uri = uri; - this.source = source; - } - -} diff --git a/core/src/main/java/cucumber/api/event/TestStepFinished.java b/core/src/main/java/cucumber/api/event/TestStepFinished.java deleted file mode 100644 index 82ffade682..0000000000 --- a/core/src/main/java/cucumber/api/event/TestStepFinished.java +++ /dev/null @@ -1,40 +0,0 @@ -package cucumber.api.event; - -import cucumber.api.HookTestStep; -import cucumber.api.PickleStepTestStep; -import cucumber.api.Result; -import cucumber.api.TestCase; -import cucumber.api.TestStep; - -/** - * A test step finished event is broadcast when ever a step finishes. - *

- * A step can either be a {@link PickleStepTestStep} or a - * {@link HookTestStep} depending on what step was executed. - *

- * Each test step finished event is followed by an matching - * {@link TestStepStarted} event for the same step.The order in which - * these events may be expected is: - *

- *     [before hook,]* [[before step hook,]* test step, [after step hook,]*]+, [after hook,]*
- * 
- * - * @see PickleStepTestStep - * @see HookTestStep - */ -public final class TestStepFinished extends TestCaseEvent { - public final TestStep testStep; - public final Result result; - - @Deprecated - public TestStepFinished(Long timeStamp, TestCase testCase, TestStep testStep, Result result) { - this(timeStamp, 0, testCase, testStep, result); - } - - public TestStepFinished(Long timeStamp, long timeStampMillis, TestCase testCase, TestStep testStep, Result result) { - super(timeStamp, timeStampMillis, testCase); - this.testStep = testStep; - this.result = result; - } - -} diff --git a/core/src/main/java/cucumber/api/event/TimeStampedEvent.java b/core/src/main/java/cucumber/api/event/TimeStampedEvent.java deleted file mode 100644 index fb40a235a6..0000000000 --- a/core/src/main/java/cucumber/api/event/TimeStampedEvent.java +++ /dev/null @@ -1,30 +0,0 @@ -package cucumber.api.event; - -abstract class TimeStampedEvent implements Event { - - private final Long timeStamp; - private final long timeStampMillis; - - TimeStampedEvent(Long timeStamp, Long timeStampMillis) { - this.timeStamp = timeStamp; - this.timeStampMillis = timeStampMillis; - } - - /** - * {@inheritDoc} - */ - @Override - public Long getTimeStamp() { - return timeStamp; - } - - /** - * Returns timestamp in milliseconds of the epoch. - * - * @return timestamp in milli seconds - * @see System#currentTimeMillis() - */ - public long getTimeStampMillis() { - return timeStampMillis; - } -} diff --git a/core/src/main/java/cucumber/api/event/WriteEvent.java b/core/src/main/java/cucumber/api/event/WriteEvent.java deleted file mode 100644 index 28ff954969..0000000000 --- a/core/src/main/java/cucumber/api/event/WriteEvent.java +++ /dev/null @@ -1,17 +0,0 @@ -package cucumber.api.event; - -import cucumber.api.TestCase; - -public final class WriteEvent extends TestCaseEvent { - public final String text; - - @Deprecated - public WriteEvent(Long timeStamp, TestCase testCase, String text) { - this(timeStamp, 0, testCase, text); - } - - public WriteEvent(Long timeStamp, long timeStampMillis, TestCase testCase, String text) { - super(timeStamp, timeStampMillis, testCase); - this.text = text; - } -} diff --git a/core/src/main/java/cucumber/api/formatter/AnsiEscapes.java b/core/src/main/java/cucumber/api/formatter/AnsiEscapes.java deleted file mode 100644 index 389488a4e3..0000000000 --- a/core/src/main/java/cucumber/api/formatter/AnsiEscapes.java +++ /dev/null @@ -1,48 +0,0 @@ -package cucumber.api.formatter; - -public class AnsiEscapes { - private static final char ESC = 27; - private static final char BRACKET = '['; - - public static AnsiEscapes RESET = color(0); - public static AnsiEscapes BLACK = color(30); - public static AnsiEscapes RED = color(31); - public static AnsiEscapes GREEN = color(32); - public static AnsiEscapes YELLOW = color(33); - public static AnsiEscapes BLUE = color(34); - public static AnsiEscapes MAGENTA = color(35); - public static AnsiEscapes CYAN = color(36); - public static AnsiEscapes WHITE = color(37); - public static AnsiEscapes DEFAULT = color(9); - public static AnsiEscapes GREY = color(90); - public static AnsiEscapes INTENSITY_BOLD = color(1); - - private static AnsiEscapes color(int code) { - return new AnsiEscapes(String.valueOf(code) + "m"); - } - - public static AnsiEscapes up(int count) { - return new AnsiEscapes(String.valueOf(count) + "A"); - } - - private final String value; - - private AnsiEscapes(String value) { - this.value = value; - } - - public void appendTo(NiceAppendable a) { - a.append(ESC).append(BRACKET).append(value); - } - - public void appendTo(StringBuilder a) { - a.append(ESC).append(BRACKET).append(value); - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - appendTo(sb); - return sb.toString(); - } -} diff --git a/core/src/main/java/cucumber/api/formatter/Formatter.java b/core/src/main/java/cucumber/api/formatter/Formatter.java deleted file mode 100644 index 4bc2389f2c..0000000000 --- a/core/src/main/java/cucumber/api/formatter/Formatter.java +++ /dev/null @@ -1,12 +0,0 @@ -package cucumber.api.formatter; - -import cucumber.api.Plugin; -import cucumber.api.event.EventListener; - -/** - * @deprecated as of version 4.0.0; use {@link EventListener } and {@link Plugin } instead. - * Optionally, use {@link ColorAware } and/or {@link StrictAware } instead of {@link Plugin }. - */ -@Deprecated -public interface Formatter extends EventListener, Plugin { -} diff --git a/core/src/main/java/cucumber/runner/EventBus.java b/core/src/main/java/cucumber/runner/EventBus.java deleted file mode 100644 index 9c58eda1ae..0000000000 --- a/core/src/main/java/cucumber/runner/EventBus.java +++ /dev/null @@ -1,16 +0,0 @@ -package cucumber.runner; - -import cucumber.api.event.Event; -import cucumber.api.event.EventPublisher; - -public interface EventBus extends EventPublisher { - - Long getTime(); - - Long getTimeMillis(); - - void send(Event event); - - void sendAll(Iterable queue); - -} diff --git a/core/src/main/java/cucumber/runner/Glue.java b/core/src/main/java/cucumber/runner/Glue.java deleted file mode 100644 index dc4de3b949..0000000000 --- a/core/src/main/java/cucumber/runner/Glue.java +++ /dev/null @@ -1,165 +0,0 @@ -package cucumber.runner; - -import cucumber.runtime.DuplicateStepDefinitionException; -import cucumber.runtime.HookDefinition; -import cucumber.runtime.ScenarioScoped; -import cucumber.runtime.StepDefinition; -import io.cucumber.stepexpression.Argument; -import cucumber.api.StepDefinitionReporter; -import cucumber.api.event.StepDefinedEvent; -import gherkin.pickles.PickleStep; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -final class Glue implements cucumber.runtime.Glue { - final Map stepDefinitionsByPattern = new TreeMap<>(); - final Map stepDefinitionsByStepText = new HashMap<>(); - final List beforeHooks = new ArrayList<>(); - final List beforeStepHooks = new ArrayList<>(); - final List afterHooks = new ArrayList<>(); - final List afterStepHooks = new ArrayList<>(); - - private final EventBus bus; - - public Glue(EventBus bus) { - this.bus = bus; - } - - @Override - public void addStepDefinition(StepDefinition stepDefinition) { - StepDefinition previous = stepDefinitionsByPattern.get(stepDefinition.getPattern()); - if (previous != null) { - throw new DuplicateStepDefinitionException(previous, stepDefinition); - } - stepDefinitionsByPattern.put(stepDefinition.getPattern(), stepDefinition); - bus.send(new StepDefinedEvent(bus.getTime(), bus.getTimeMillis(), stepDefinition)); - } - - @Override - public void addBeforeHook(HookDefinition hookDefinition) { - beforeHooks.add(hookDefinition); - Collections.sort(beforeHooks, new HookComparator(true)); - } - - @Override - public void addBeforeStepHook(HookDefinition hookDefinition) { - beforeStepHooks.add(hookDefinition); - Collections.sort(beforeStepHooks, new HookComparator(true)); - } - @Override - public void addAfterHook(HookDefinition hookDefinition) { - afterHooks.add(hookDefinition); - Collections.sort(afterHooks, new HookComparator(false)); - } - - @Override - public void addAfterStepHook(HookDefinition hookDefinition) { - afterStepHooks.add(hookDefinition); - Collections.sort(afterStepHooks, new HookComparator(false)); - } - - List getBeforeHooks() { - return beforeHooks; - } - - List getBeforeStepHooks() { - return beforeStepHooks; - } - - List getAfterHooks() { - return afterHooks; - } - - List getAfterStepHooks() { - return afterStepHooks; - } - - PickleStepDefinitionMatch stepDefinitionMatch(String featurePath, PickleStep step) { - String stepText = step.getText(); - - StepDefinition stepDefinition = stepDefinitionsByStepText.get(stepText); - if (stepDefinition != null) { - // Step definition arguments consists of parameters included in the step text and - // gherkin step arguments (doc string and data table) which are not included in - // the step text. As such the step definition arguments can not be cached and - // must be recreated each time. - List arguments = stepDefinition.matchedArguments(step); - return new PickleStepDefinitionMatch(arguments, stepDefinition, featurePath, step); - } - - List matches = stepDefinitionMatches(featurePath, step); - if (matches.isEmpty()) { - return null; - } - if (matches.size() > 1) { - throw new AmbiguousStepDefinitionsException(step, matches); - } - - PickleStepDefinitionMatch match = matches.get(0); - - stepDefinitionsByStepText.put(stepText, match.getStepDefinition()); - - return match; - } - - private List stepDefinitionMatches(String featurePath, PickleStep step) { - List result = new ArrayList(); - for (StepDefinition stepDefinition : stepDefinitionsByPattern.values()) { - List arguments = stepDefinition.matchedArguments(step); - if (arguments != null) { - result.add(new PickleStepDefinitionMatch(arguments, stepDefinition, featurePath, step)); - } - } - return result; - } - - void reportStepDefinitions(StepDefinitionReporter stepDefinitionReporter) { - for (StepDefinition stepDefinition : stepDefinitionsByPattern.values()) { - stepDefinitionReporter.stepDefinition(stepDefinition); - } - } - - @Override - public void removeScenarioScopedGlue() { - removeScenarioScopedHooks(beforeHooks); - removeScenarioScopedHooks(beforeStepHooks); - removeScenarioScopedHooks(afterHooks); - removeScenarioScopedHooks(afterStepHooks); - removeScenariosScopedStepDefinitions(stepDefinitionsByPattern); - removeScenariosScopedStepDefinitions(stepDefinitionsByStepText); - } - - private void removeScenarioScopedHooks(List beforeHooks) { - Iterator hookIterator = beforeHooks.iterator(); - while (hookIterator.hasNext()) { - HookDefinition hook = hookIterator.next(); - if (hook instanceof ScenarioScoped) { - ScenarioScoped scenarioScopedHookDefinition = (ScenarioScoped) hook; - scenarioScopedHookDefinition.disposeScenarioScope(); - } - if (hook.isScenarioScoped()) { - hookIterator.remove(); - } - } - } - - private void removeScenariosScopedStepDefinitions(Map stepDefinitions) { - Iterator> stepDefinitionIterator = stepDefinitions.entrySet().iterator(); - while(stepDefinitionIterator.hasNext()){ - StepDefinition stepDefinition = stepDefinitionIterator.next().getValue(); - if (stepDefinition instanceof ScenarioScoped) { - ScenarioScoped scenarioScopedStepDefinition = (ScenarioScoped) stepDefinition; - scenarioScopedStepDefinition.disposeScenarioScope(); - } - if(stepDefinition.isScenarioScoped()){ - stepDefinitionIterator.remove(); - } - } - } -} diff --git a/core/src/main/java/cucumber/runner/HookComparator.java b/core/src/main/java/cucumber/runner/HookComparator.java deleted file mode 100644 index 29e8227fc5..0000000000 --- a/core/src/main/java/cucumber/runner/HookComparator.java +++ /dev/null @@ -1,20 +0,0 @@ -package cucumber.runner; - -import cucumber.runtime.HookDefinition; - -import java.util.Comparator; - -class HookComparator implements Comparator { - private final boolean ascending; - - HookComparator(boolean ascending) { - this.ascending = ascending; - } - - @Override - public int compare(HookDefinition hook1, HookDefinition hook2) { - int x = hook1.getOrder(); - int y = hook2.getOrder(); - return ascending ? Integer.compare(x, y) : Integer.compare(y, x); - } -} diff --git a/core/src/main/java/cucumber/runner/Runner.java b/core/src/main/java/cucumber/runner/Runner.java deleted file mode 100644 index 62633fceb2..0000000000 --- a/core/src/main/java/cucumber/runner/Runner.java +++ /dev/null @@ -1,140 +0,0 @@ -package cucumber.runner; - -import cucumber.api.HookType; -import cucumber.api.StepDefinitionReporter; -import cucumber.api.event.SnippetsSuggestedEvent; -import cucumber.runtime.Backend; -import cucumber.runtime.HookDefinition; -import cucumber.util.FixJava; -import gherkin.events.PickleEvent; -import gherkin.pickles.PickleStep; -import gherkin.pickles.PickleTag; -import io.cucumber.core.logging.Logger; -import io.cucumber.core.logging.LoggerFactory; -import io.cucumber.core.options.RunnerOptions; - -import java.net.URI; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -public final class Runner { - - private static final Logger log = LoggerFactory.getLogger(Runner.class); - - private final Glue glue; - private final EventBus bus; - private final Collection backends; - private final RunnerOptions runnerOptions; - - public Runner(EventBus bus, Collection backends, RunnerOptions runnerOptions) { - this.bus = bus; - this.glue = new Glue(bus); - this.runnerOptions = runnerOptions; - this.backends = backends; - List gluePaths = runnerOptions.getGlue(); - log.debug("Loading glue from " + FixJava.join(gluePaths, ", ")); - for (Backend backend : backends) { - log.debug("Loading glue for backend " + backend.getClass().getName()); - backend.loadGlue(this.glue, gluePaths); - } - } - - public EventBus getBus() { - return bus; - } - - public void runPickle(PickleEvent pickle) { - buildBackendWorlds(); // Java8 step definitions will be added to the glue here - TestCase testCase = createTestCaseForPickle(pickle); - testCase.run(bus); - disposeBackendWorlds(); - } - - public void reportStepDefinitions(StepDefinitionReporter stepDefinitionReporter) { - glue.reportStepDefinitions(stepDefinitionReporter); - } - - private TestCase createTestCaseForPickle(PickleEvent pickleEvent) { - List testSteps = new ArrayList<>(); - List beforeHooks = new ArrayList<>(); - List afterHooks = new ArrayList<>(); - if (!pickleEvent.pickle.getSteps().isEmpty()) { - addTestStepsForBeforeHooks(beforeHooks, pickleEvent.pickle.getTags()); - addTestStepsForPickleSteps(testSteps, pickleEvent); - addTestStepsForAfterHooks(afterHooks, pickleEvent.pickle.getTags()); - } - return new TestCase(testSteps, beforeHooks, afterHooks, pickleEvent, runnerOptions.isDryRun()); - } - - private void addTestStepsForPickleSteps(List testSteps, PickleEvent pickleEvent) { - for (PickleStep step : pickleEvent.pickle.getSteps()) { - PickleStepDefinitionMatch match; - try { - match = glue.stepDefinitionMatch(pickleEvent.uri, step); - if (match == null) { - List snippets = new ArrayList<>(); - for (Backend backend : backends) { - List snippet = backend.getSnippet(step, "**KEYWORD**", runnerOptions.getSnippetType().getFunctionNameGenerator()); - snippets.addAll(snippet); - } - if (!snippets.isEmpty()) { - bus.send(new SnippetsSuggestedEvent(bus.getTime(), bus.getTimeMillis(), pickleEvent.uri, step.getLocations(), snippets)); - } - match = new UndefinedPickleStepDefinitionMatch(step); - } - } catch (AmbiguousStepDefinitionsException e) { - match = new AmbiguousPickleStepDefinitionsMatch(pickleEvent.uri, step, e); - } catch (Throwable t) { - match = new FailedPickleStepInstantiationMatch(pickleEvent.uri, step, t); - } - - - List afterStepHookSteps = getAfterStepHooks(pickleEvent.pickle.getTags()); - List beforeStepHookSteps = getBeforeStepHooks(pickleEvent.pickle.getTags()); - testSteps.add(new PickleStepTestStep(pickleEvent.uri, step, beforeStepHookSteps, afterStepHookSteps, match)); - } - } - - private void addTestStepsForBeforeHooks(List testSteps, List tags) { - addTestStepsForHooks(testSteps, tags, glue.getBeforeHooks(), HookType.Before); - } - - private void addTestStepsForAfterHooks(List testSteps, List tags) { - addTestStepsForHooks(testSteps, tags, glue.getAfterHooks(), HookType.After); - } - - private void addTestStepsForHooks(List testSteps, List tags, List hooks, HookType hookType) { - for (HookDefinition hook : hooks) { - if (hook.matches(tags)) { - HookTestStep testStep = new HookTestStep(hookType, new HookDefinitionMatch(hook)); - testSteps.add(testStep); - } - } - } - - private List getAfterStepHooks(List tags) { - List hookSteps = new ArrayList<>(); - addTestStepsForHooks(hookSteps, tags, glue.getAfterStepHooks(), HookType.AfterStep); - return hookSteps; - } - - private List getBeforeStepHooks(List tags) { - List hookSteps = new ArrayList<>(); - addTestStepsForHooks(hookSteps, tags, glue.getBeforeStepHooks(), HookType.BeforeStep); - return hookSteps; - } - - private void buildBackendWorlds() { - for (Backend backend : backends) { - backend.buildWorld(); - } - } - - private void disposeBackendWorlds() { - for (Backend backend : backends) { - backend.disposeWorld(); - } - glue.removeScenarioScopedGlue(); - } -} diff --git a/core/src/main/java/cucumber/runner/RunnerSupplier.java b/core/src/main/java/cucumber/runner/RunnerSupplier.java deleted file mode 100644 index cd7fe3275a..0000000000 --- a/core/src/main/java/cucumber/runner/RunnerSupplier.java +++ /dev/null @@ -1,5 +0,0 @@ -package cucumber.runner; - -public interface RunnerSupplier { - Runner get(); -} diff --git a/core/src/main/java/cucumber/runner/Scenario.java b/core/src/main/java/cucumber/runner/Scenario.java deleted file mode 100644 index d17ce89d0d..0000000000 --- a/core/src/main/java/cucumber/runner/Scenario.java +++ /dev/null @@ -1,97 +0,0 @@ -package cucumber.runner; - -import cucumber.api.Result; -import cucumber.api.event.EmbedEvent; -import cucumber.api.event.WriteEvent; -import gherkin.pickles.PickleTag; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import static java.util.Collections.max; - -class Scenario implements cucumber.api.Scenario { - - private final List stepResults = new ArrayList(); - private final EventBus bus; - private final TestCase testCase; - - Scenario(EventBus bus, TestCase testCase) { - this.bus = bus; - this.testCase = testCase; - } - - public void add(Result result) { - stepResults.add(result); - } - - @Override - public Collection getSourceTagNames() { - Set result = new HashSet<>(); - for (PickleTag tag : testCase.getTags()) { - result.add(tag.getName()); - } - // Has to be a List in order for JRuby to convert to Ruby Array. - return new ArrayList<>(result); - } - - @Override - public Result.Type getStatus() { - if (stepResults.isEmpty()) { - return Result.Type.UNDEFINED; - } - - return max(stepResults, Result.SEVERITY).getStatus(); - } - - @Override - public boolean isFailed() { - return getStatus() == Result.Type.FAILED; - } - - @Override - public void embed(byte[] data, String mimeType) { - bus.send(new EmbedEvent(bus.getTime(), bus.getTimeMillis(), testCase, data, mimeType)); - } - - @Override - public void embed(byte[] data, String mimeType, String name) { - bus.send(new EmbedEvent(bus.getTime(), bus.getTimeMillis(), testCase, data, mimeType, name)); - } - - @Override - public void write(String text) { - bus.send(new WriteEvent(bus.getTime(), bus.getTimeMillis(), testCase, text)); - } - - @Override - public String getName() { - return testCase.getName(); - } - - @Override - public String getId() { - return testCase.getUri() + ":" + testCase.getLine(); - } - - @Override - public String getUri() { - return testCase.getUri(); - } - - @Override - public List getLines() { - return testCase.getLines(); - } - - public Throwable getError() { - if (stepResults.isEmpty()) { - return null; - } - - return max(stepResults, Result.SEVERITY).getError(); - } -} diff --git a/core/src/main/java/cucumber/runner/SingletonRunnerSupplier.java b/core/src/main/java/cucumber/runner/SingletonRunnerSupplier.java deleted file mode 100644 index 6dfbe45664..0000000000 --- a/core/src/main/java/cucumber/runner/SingletonRunnerSupplier.java +++ /dev/null @@ -1,41 +0,0 @@ -package cucumber.runner; - -import cucumber.runtime.BackendSupplier; -import io.cucumber.core.options.RunnerOptions; - -/** - * Returns a single unique runner. - * - * Not thread safe. - */ -public class SingletonRunnerSupplier implements RunnerSupplier { - - private final BackendSupplier backendSupplier; - private final RunnerOptions runnerOptions; - private final EventBus eventBus; - private Runner runner; - - - public SingletonRunnerSupplier( - RunnerOptions runnerOptions, - EventBus eventBus, - BackendSupplier backendSupplier - ) { - this.backendSupplier = backendSupplier; - this.runnerOptions = runnerOptions; - this.eventBus = eventBus; - } - - @Override - public Runner get() { - if (runner == null) { - runner = createRunner(); - } - return runner; - } - - private Runner createRunner() { - return new Runner(eventBus, backendSupplier.get(), runnerOptions); - } - -} diff --git a/core/src/main/java/cucumber/runner/TimeService.java b/core/src/main/java/cucumber/runner/TimeService.java deleted file mode 100644 index 634633dbca..0000000000 --- a/core/src/main/java/cucumber/runner/TimeService.java +++ /dev/null @@ -1,19 +0,0 @@ -package cucumber.runner; - -public interface TimeService { - long time(); - long timeMillis(); - - TimeService SYSTEM = new TimeService() { - @Override - public long time() { - return System.nanoTime(); - } - - @Override - public long timeMillis() { - return System.currentTimeMillis(); - } - }; - -} diff --git a/core/src/main/java/cucumber/runner/TimeServiceEventBus.java b/core/src/main/java/cucumber/runner/TimeServiceEventBus.java deleted file mode 100644 index c0874da8b9..0000000000 --- a/core/src/main/java/cucumber/runner/TimeServiceEventBus.java +++ /dev/null @@ -1,19 +0,0 @@ -package cucumber.runner; - -public final class TimeServiceEventBus extends AbstractEventBus { - private final TimeService stopWatch; - - public TimeServiceEventBus(TimeService stopWatch) { - this.stopWatch = stopWatch; - } - - @Override - public Long getTime() { - return stopWatch.time(); - } - - @Override - public Long getTimeMillis() { - return stopWatch.timeMillis(); - } -} diff --git a/core/src/main/java/cucumber/runner/UndefinedStepException.java b/core/src/main/java/cucumber/runner/UndefinedStepException.java deleted file mode 100644 index 7bcd461aa8..0000000000 --- a/core/src/main/java/cucumber/runner/UndefinedStepException.java +++ /dev/null @@ -1,9 +0,0 @@ -package cucumber.runner; - -import gherkin.pickles.PickleStep; - -final class UndefinedStepException extends Throwable { - public UndefinedStepException(PickleStep step) { - super(String.format("Undefined Step: %s", step.getText())); - } -} diff --git a/core/src/main/java/cucumber/runtime/Backend.java b/core/src/main/java/cucumber/runtime/Backend.java deleted file mode 100644 index 7cf58e91ad..0000000000 --- a/core/src/main/java/cucumber/runtime/Backend.java +++ /dev/null @@ -1,27 +0,0 @@ -package cucumber.runtime; - -import cucumber.runtime.snippets.FunctionNameGenerator; -import gherkin.pickles.PickleStep; - -import java.net.URI; -import java.util.List; - -public interface Backend { - /** - * Invoked once before all features. This is where stepdefs and hooks should be loaded. - */ - void loadGlue(Glue glue, List gluePaths); - - /** - * Invoked before a new scenario starts. Implementations should do any necessary - * setup of new, isolated state here. - */ - void buildWorld(); - - /** - * Invoked at the end of a scenario, after hooks - */ - void disposeWorld(); - - List getSnippet(PickleStep step, String keyword, FunctionNameGenerator functionNameGenerator); -} diff --git a/core/src/main/java/cucumber/runtime/BackendModuleBackendSupplier.java b/core/src/main/java/cucumber/runtime/BackendModuleBackendSupplier.java deleted file mode 100644 index b8d2311edf..0000000000 --- a/core/src/main/java/cucumber/runtime/BackendModuleBackendSupplier.java +++ /dev/null @@ -1,64 +0,0 @@ -package cucumber.runtime; - -import cucumber.api.TypeRegistryConfigurer; -import cucumber.runtime.io.ResourceLoader; -import io.cucumber.core.options.RunnerOptions; -import io.cucumber.stepexpression.TypeRegistry; - -import java.net.URI; -import java.util.Collection; -import java.util.List; - -import static java.util.Collections.singletonList; - - -/** - * Supplies instances of {@link Backend} found by scanning {@code cucumber.runtime} for implementations. - */ -public final class BackendModuleBackendSupplier implements BackendSupplier { - - private static final List backendPackages = singletonList(URI.create("classpath:cucumber/runtime")); - private final ResourceLoader resourceLoader; - private final ClassFinder classFinder; - private final RunnerOptions runnerOptions; - private final List packages; - - public BackendModuleBackendSupplier(ResourceLoader resourceLoader, ClassFinder classFinder, RunnerOptions runnerOptions) { - this(resourceLoader, classFinder, runnerOptions, backendPackages); - } - - BackendModuleBackendSupplier(ResourceLoader resourceLoader, ClassFinder classFinder, RunnerOptions runnerOptions, List packages) { - this.resourceLoader = resourceLoader; - this.classFinder = classFinder; - this.runnerOptions = runnerOptions; - this.packages = packages; - } - - @Override - public Collection get() { - Collection backends = loadBackends(); - if (backends.isEmpty()) { - throw new CucumberException("No backends were found. Please make sure you have a backend module on your CLASSPATH."); - } - return backends; - } - - private Collection loadBackends() { - Reflections reflections = new Reflections(classFinder); - - TypeRegistry typeRegistry; - - io.cucumber.core.api.TypeRegistryConfigurer typeRegistryConfigurer = reflections.instantiateExactlyOneSubclass(io.cucumber.core.api.TypeRegistryConfigurer.class, runnerOptions.getGlue(), new Class[0], new Object[0], new DefaultTypeRegistryConfiguration()); - if (typeRegistryConfigurer.getClass() != DefaultTypeRegistryConfiguration.class) { - typeRegistry = new TypeRegistry(typeRegistryConfigurer.locale()); - typeRegistryConfigurer.configureTypeRegistry(typeRegistry); - } else { - TypeRegistryConfigurer typeRegistryConfigurer2 = reflections.instantiateExactlyOneSubclass(TypeRegistryConfigurer.class, runnerOptions.getGlue(), new Class[0], new Object[0], new DefaultTypeRegistryConfiguration()); - typeRegistry = new TypeRegistry(typeRegistryConfigurer2.locale()); - typeRegistryConfigurer2.configureTypeRegistry(typeRegistry); - } - - return reflections.instantiateSubclasses(Backend.class, packages, new Class[]{ResourceLoader.class, TypeRegistry.class}, new Object[]{resourceLoader, typeRegistry}); - } - -} diff --git a/core/src/main/java/cucumber/runtime/DefaultTypeRegistryConfiguration.java b/core/src/main/java/cucumber/runtime/DefaultTypeRegistryConfiguration.java deleted file mode 100644 index 939b5c6ce2..0000000000 --- a/core/src/main/java/cucumber/runtime/DefaultTypeRegistryConfiguration.java +++ /dev/null @@ -1,25 +0,0 @@ -package cucumber.runtime; - -import cucumber.api.TypeRegistryConfigurer; -import cucumber.api.TypeRegistry; - -import java.util.Locale; - -public class DefaultTypeRegistryConfiguration implements TypeRegistryConfigurer, io.cucumber.core.api.TypeRegistryConfigurer { - - @Override - public Locale locale() { - return Locale.ENGLISH; - } - - @Override - public void configureTypeRegistry(io.cucumber.core.api.TypeRegistry typeRegistry) { - //noop - } - - @Override - public void configureTypeRegistry(TypeRegistry typeRegistry) { - //noop - } - -} diff --git a/core/src/main/java/cucumber/runtime/DuplicateStepDefinitionException.java b/core/src/main/java/cucumber/runtime/DuplicateStepDefinitionException.java deleted file mode 100644 index f62890b807..0000000000 --- a/core/src/main/java/cucumber/runtime/DuplicateStepDefinitionException.java +++ /dev/null @@ -1,11 +0,0 @@ -package cucumber.runtime; - -public class DuplicateStepDefinitionException extends CucumberException { - public DuplicateStepDefinitionException(StepDefinition a, StepDefinition b) { - super(createMessage(a, b)); - } - - private static String createMessage(StepDefinition a, StepDefinition b) { - return String.format("Duplicate step definitions in %s and %s", a.getLocation(true), b.getLocation(true)); - } -} diff --git a/core/src/main/java/cucumber/runtime/Env.java b/core/src/main/java/cucumber/runtime/Env.java deleted file mode 100644 index 81bea903ec..0000000000 --- a/core/src/main/java/cucumber/runtime/Env.java +++ /dev/null @@ -1,73 +0,0 @@ -package cucumber.runtime; - -import java.util.HashMap; -import java.util.Map; -import java.util.MissingResourceException; -import java.util.Properties; -import java.util.ResourceBundle; - -import static java.util.Locale.ROOT; - -/** - * Looks up values in the following order: - *
    - *
  1. Environment variable
  2. - *
  3. System property
  4. - *
  5. Resource bundle
  6. - *
- */ -public class Env { - public static final Env INSTANCE = new Env("cucumber"); - private final Map map = new HashMap(); - - public Env() { - this(null, System.getProperties()); - } - - public Env(String bundleName) { - this(bundleName, System.getProperties()); - } - - public Env(Properties properties) { - this(null, properties); - } - - public Env(String bundleName, Properties properties) { - if (bundleName != null) { - try { - ResourceBundle bundle = ResourceBundle.getBundle(bundleName); - for (String key : bundle.keySet()) { - put(key, bundle.getString(key)); - } - } catch (MissingResourceException ignore) { - } - } - - if (properties != null) { - for (String key : properties.stringPropertyNames()) { - put(key, properties.getProperty(key)); - } - } - - Map env = System.getenv(); - for (String key : env.keySet()) { - put(key, env.get(key)); - } - } - - private void put(String key, String string) { - map.put(key, string); - // Support old skool - map.put(key.replace('.', '_').toUpperCase(ROOT), string); - map.put(key.replace('_', '.').toLowerCase(ROOT), string); - } - - public String get(String key) { - return map.get(key); - } - - public String get(String key, String defaultValue) { - String result = get(key); - return result != null ? result : defaultValue; - } -} diff --git a/core/src/main/java/cucumber/runtime/ExitStatus.java b/core/src/main/java/cucumber/runtime/ExitStatus.java deleted file mode 100755 index 90c7c0618d..0000000000 --- a/core/src/main/java/cucumber/runtime/ExitStatus.java +++ /dev/null @@ -1,49 +0,0 @@ -package cucumber.runtime; - -import cucumber.api.Result; -import cucumber.api.event.EventHandler; -import cucumber.api.event.EventListener; -import cucumber.api.event.EventPublisher; -import cucumber.api.event.TestCaseFinished; -import io.cucumber.core.options.RuntimeOptions; - -import java.util.ArrayList; -import java.util.List; - -import static cucumber.api.Result.SEVERITY; -import static java.util.Collections.max; -import static java.util.Collections.min; - -public class ExitStatus implements EventListener { - private static final byte DEFAULT = 0x0; - private static final byte ERRORS = 0x1; - - private final List results = new ArrayList(); - private final RuntimeOptions runtimeOptions; - - private final EventHandler testCaseFinishedHandler = new EventHandler() { - @Override - public void receive(TestCaseFinished event) { - results.add(event.result); - } - }; - - public ExitStatus(RuntimeOptions runtimeOptions) { - this.runtimeOptions = runtimeOptions; - } - - @Override - public void setEventPublisher(EventPublisher publisher) { - publisher.registerHandlerFor(TestCaseFinished.class, testCaseFinishedHandler); - } - - public byte exitStatus() { - if (results.isEmpty()) { return DEFAULT; } - - if (runtimeOptions.isWip()) { - return min(results, SEVERITY).is(Result.Type.PASSED) ? ERRORS : DEFAULT; - } - - return max(results, SEVERITY).isOk(runtimeOptions.isStrict()) ? DEFAULT : ERRORS; - } -} diff --git a/core/src/main/java/cucumber/runtime/Glue.java b/core/src/main/java/cucumber/runtime/Glue.java deleted file mode 100644 index f8db770287..0000000000 --- a/core/src/main/java/cucumber/runtime/Glue.java +++ /dev/null @@ -1,16 +0,0 @@ -package cucumber.runtime; - -public interface Glue { - - void addStepDefinition(StepDefinition stepDefinition) throws DuplicateStepDefinitionException; - - void addBeforeHook(HookDefinition hookDefinition); - - void addAfterHook(HookDefinition hookDefinition); - - void addBeforeStepHook(HookDefinition beforeStepHook); - - void addAfterStepHook(HookDefinition hookDefinition); - - void removeScenarioScopedGlue(); -} diff --git a/core/src/main/java/cucumber/runtime/GlueSupplier.java b/core/src/main/java/cucumber/runtime/GlueSupplier.java deleted file mode 100644 index 623fb5f47f..0000000000 --- a/core/src/main/java/cucumber/runtime/GlueSupplier.java +++ /dev/null @@ -1,5 +0,0 @@ -package cucumber.runtime; - -public interface GlueSupplier { - Glue get(); -} diff --git a/core/src/main/java/cucumber/runtime/StepDefinition.java b/core/src/main/java/cucumber/runtime/StepDefinition.java deleted file mode 100644 index 82d3c48d9a..0000000000 --- a/core/src/main/java/cucumber/runtime/StepDefinition.java +++ /dev/null @@ -1,52 +0,0 @@ -package cucumber.runtime; - -import io.cucumber.stepexpression.Argument; -import gherkin.pickles.PickleStep; - -import java.util.List; - -public interface StepDefinition { - /** - * Returns a list of arguments. Return null if the step definition - * doesn't match at all. Return an empty List if it matches with 0 arguments - * and bigger sizes if it matches several. - */ - List matchedArguments(PickleStep step); - - /** - * The source line where the step definition is defined. - * Example: foo/bar/Zap.brainfuck:42 - * - * @param detail true if extra detailed location information should be included. - */ - String getLocation(boolean detail); - - /** - * How many declared parameters this step definition has. Returns null if unknown. - */ - Integer getParameterCount(); - - /** - * Invokes the step definition. The method should raise a Throwable - * if the invocation fails, which will cause the step to fail. - */ - void execute(Object[] args) throws Throwable; - - /** - * Return true if this matches the location. This is used to filter - * stack traces. - */ - boolean isDefinedAt(StackTraceElement stackTraceElement); // TODO: redundant with getLocation? - - /** - * @return the pattern associated with this instance. Used for error reporting only. - */ - String getPattern(); - - /** - * @deprecated replaced with {@link ScenarioScoped} - * @return true if this instance is scoped to a single scenario, or false if it can be reused across scenarios. - */ - @Deprecated - boolean isScenarioScoped(); -} diff --git a/core/src/main/java/cucumber/runtime/Supplier.java b/core/src/main/java/cucumber/runtime/Supplier.java deleted file mode 100644 index 7ee00439ae..0000000000 --- a/core/src/main/java/cucumber/runtime/Supplier.java +++ /dev/null @@ -1,7 +0,0 @@ -package cucumber.runtime; - -public interface Supplier { - - T get(); - -} diff --git a/core/src/main/java/cucumber/runtime/Timeout.java b/core/src/main/java/cucumber/runtime/Timeout.java deleted file mode 100644 index 2bc310edfc..0000000000 --- a/core/src/main/java/cucumber/runtime/Timeout.java +++ /dev/null @@ -1,63 +0,0 @@ -package cucumber.runtime; - -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; - -public class Timeout { - private Timeout() { - } - - public static T timeout(Callback callback, long timeoutMillis) throws Throwable { - if (timeoutMillis == 0) { - return callback.call(); - } - - /* We need to ensure a happens before relation exists between these events; - * a. the timer setting the interrupt flag on the execution thread. - * b. terminating and cleaning up the timer - * To do this we synchronize on monitor. The atomic boolean is merely a convenient container. - */ - final Thread executionThread = Thread.currentThread(); - final Object monitor = new Object(); - final AtomicBoolean done = new AtomicBoolean(); - - ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); - ScheduledFuture timer = executorService.schedule(new Runnable() { - @Override - public void run() { - synchronized (monitor) { - if (!done.get()) { - executionThread.interrupt(); - } - } - } - }, timeoutMillis, TimeUnit.MILLISECONDS); - - try { - T result = callback.call(); - // The callback may have been busy waiting. - if (Thread.interrupted()) { - throw new TimeoutException("Timed out after " + timeoutMillis + "ms."); - } - return result; - } catch (InterruptedException timeout) { - throw new TimeoutException("Timed out after " + timeoutMillis + "ms."); - } finally { - synchronized (monitor) { - done.set(true); - timer.cancel(true); - executorService.shutdownNow(); - // Clear the interrupted flag. It may have been set by the timer just before we returned the result. - Thread.interrupted(); - } - } - } - - public interface Callback { - T call() throws Throwable; - } -} diff --git a/core/src/main/java/cucumber/runtime/TooManyInstancesException.java b/core/src/main/java/cucumber/runtime/TooManyInstancesException.java deleted file mode 100644 index 463f01211e..0000000000 --- a/core/src/main/java/cucumber/runtime/TooManyInstancesException.java +++ /dev/null @@ -1,14 +0,0 @@ -package cucumber.runtime; - -import java.util.Collection; - -public class TooManyInstancesException extends CucumberException { - - public TooManyInstancesException(Collection instances) { - super(createMessage(instances)); - } - - private static String createMessage(Collection instances) { - return String.format("Expected only one instance, but found too many: " + instances); - } -} diff --git a/core/src/main/java/cucumber/runtime/Utils.java b/core/src/main/java/cucumber/runtime/Utils.java deleted file mode 100644 index 33f998204a..0000000000 --- a/core/src/main/java/cucumber/runtime/Utils.java +++ /dev/null @@ -1,104 +0,0 @@ -package cucumber.runtime; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.net.MalformedURLException; -import java.net.URL; - -public class Utils { - private Utils() { - } - - public static boolean isInstantiable(Class clazz) { - boolean isNonStaticInnerClass = !Modifier.isStatic(clazz.getModifiers()) && clazz.getEnclosingClass() != null; - return Modifier.isPublic(clazz.getModifiers()) && !Modifier.isAbstract(clazz.getModifiers()) && !isNonStaticInnerClass; - } - - public static Object invoke(final Object target, final Method method, long timeoutMillis, final Object... args) throws Throwable { - final Method targetMethod = targetMethod(target, method); - return Timeout.timeout(new Timeout.Callback() { - @Override - public Object call() throws Throwable { - boolean accessible = targetMethod.isAccessible(); - try { - targetMethod.setAccessible(true); - return targetMethod.invoke(target, args); - } catch (IllegalArgumentException e) { - throw new CucumberException("Failed to invoke " + MethodFormat.FULL.format(targetMethod) + - ", caused by " + e.getClass().getName() + ": " + e.getMessage(), e); - } catch (InvocationTargetException e) { - throw e.getTargetException(); - } catch (IllegalAccessException e) { - throw new CucumberException("Failed to invoke " + MethodFormat.FULL.format(targetMethod) + - ", caused by " + e.getClass().getName() + ": " + e.getMessage(), e); - } finally { - targetMethod.setAccessible(accessible); - } - } - }, timeoutMillis); - } - - private static Method targetMethod(final Object target, final Method method) throws NoSuchMethodException { - final Class targetClass = target.getClass(); - final Class declaringClass = method.getDeclaringClass(); - - // Immediately return the provided method if the class loaders are the same. - if (targetClass.getClassLoader().equals(declaringClass.getClassLoader())) { - return method; - } else { - // Check if the method is publicly accessible. Note that methods from interfaces are always public. - if (Modifier.isPublic(method.getModifiers())) { - return targetClass.getMethod(method.getName(), method.getParameterTypes()); - } - - // Loop through all the super classes until the declared method is found. - Class currentClass = targetClass; - while (currentClass != Object.class) { - try { - return currentClass.getDeclaredMethod(method.getName(), method.getParameterTypes()); - } catch (NoSuchMethodException e) { - currentClass = currentClass.getSuperclass(); - } - } - - // The method does not exist in the class hierarchy. - throw new NoSuchMethodException(String.valueOf(method)); - } - } - - public static URL toURL(String pathOrUrl) { - try { - if (!pathOrUrl.endsWith("/")) { - pathOrUrl = pathOrUrl + "/"; - } - if (pathOrUrl.matches("^(file|http|https):.*")) { - return new URL(pathOrUrl); - } else { - return new URL("file:" + pathOrUrl); - } - } catch (MalformedURLException e) { - throw new CucumberException("Bad URL:" + pathOrUrl, e); - } - } - - public static String htmlEscape(String s) { - // https://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet - return s - .replace("&", "&") - .replace("<", "<") - .replace(">", ">") - .replace("\"", """) - .replace("'", "'") - .replace("/", "/"); - } - - public static String getUniqueTestNameForScenarioExample(String testCaseName, int exampleNumber) { - return testCaseName + (includesBlank(testCaseName) ? " " : "_") + exampleNumber; - } - - private static boolean includesBlank(String testCaseName) { - return testCaseName.indexOf(' ') != -1; - } - -} \ No newline at end of file diff --git a/core/src/main/java/cucumber/runtime/autocomplete/MetaStepdef.java b/core/src/main/java/cucumber/runtime/autocomplete/MetaStepdef.java deleted file mode 100644 index e68683fd3d..0000000000 --- a/core/src/main/java/cucumber/runtime/autocomplete/MetaStepdef.java +++ /dev/null @@ -1,95 +0,0 @@ -package cucumber.runtime.autocomplete; - -import gherkin.deps.com.google.gson.Gson; -import gherkin.deps.com.google.gson.GsonBuilder; - -import java.util.ArrayList; -import java.util.List; -import java.util.SortedSet; -import java.util.TreeSet; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class MetaStepdef { - private static final Gson GSON = new GsonBuilder().create(); - - public final SortedSet steps = new TreeSet(); - public String source; - public String flags; - private transient Pattern pattern; - - public boolean matches(String text) { - Pattern p = pattern(); - Matcher m = p.matcher(text); - return m.matches() || m.hitEnd(); - } - - private Pattern pattern() { - if (pattern == null) { - pattern = Pattern.compile(source); - } - return pattern; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - MetaStepdef that = (MetaStepdef) o; - - if (!flags.equals(that.flags)) return false; - if (!source.equals(that.source)) return false; - if (!steps.equals(that.steps)) return false; - - return true; - } - - @Override - public int hashCode() { - int result = steps.hashCode(); - result = 31 * result + source.hashCode(); - result = 31 * result + flags.hashCode(); - return result; - } - - @Override - public String toString() { - return GSON.toJson(this); - } - - public static class MetaStep implements Comparable { - public String name; - public final List args = new ArrayList(); - - @Override - public int compareTo(MetaStep other) { - return name.compareTo(other.name); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - MetaStep metaStep = (MetaStep) o; - - if (!args.equals(metaStep.args)) return false; - if (!name.equals(metaStep.name)) return false; - - return true; - } - - @Override - public int hashCode() { - int result = name.hashCode(); - result = 31 * result + args.hashCode(); - return result; - } - } - - public static class MetaArgument { - public int offset; - public String val; - } -} diff --git a/core/src/main/java/cucumber/runtime/filter/TagExpressionOld.java b/core/src/main/java/cucumber/runtime/filter/TagExpressionOld.java deleted file mode 100644 index 106c6ea811..0000000000 --- a/core/src/main/java/cucumber/runtime/filter/TagExpressionOld.java +++ /dev/null @@ -1,166 +0,0 @@ -package cucumber.runtime.filter; - -import gherkin.pickles.PickleTag; -import io.cucumber.core.logging.Logger; -import io.cucumber.core.logging.LoggerFactory; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -class TagExpressionOld { - - private static final Logger logger = LoggerFactory.getLogger(TagExpressionOld.class); - - private final Map limits = new HashMap(); - private And and = new And(); - - static boolean isOldTagExpression(String tagExpression) { - if (tagExpression == null) { - return false; - } - if (tagExpression.contains(",")) { - logger.warn("Found tags option '" + tagExpression + "'. Support for '@tag1,@tag2' will be removed from the next release of Cucumber-JVM. Please use '@tag or @tag2' instead"); - } - if (tagExpression.contains("~")) { - logger.warn("Found tags option '" + tagExpression + "'. Support for '~@tag' will be removed from the next release of Cucumber-JVM. Please use 'not @tag' instead."); - } - return tagExpression.contains(",") || tagExpression.contains("~"); - } - - TagExpressionOld(List tagExpressions) { - for (String tagExpression : tagExpressions) { - add(tagExpression.split("\\s*,\\s*")); - } - } - - boolean evaluate(Collection tags) { - return and.isEmpty() || and.eval(tags); - } - - public Map limits() { - return limits; - } - - public boolean isEmpty() { - return and.isEmpty(); - } - - private void add(String[] tags) { - Or or = new Or(); - for (String tag : tags) { - boolean negation; - tag = tag.trim(); - if (tag.startsWith("~")) { - tag = tag.substring(1); - negation = true; - } else { - negation = false; - } - String[] tagAndLimit = tag.split(":"); - if (tagAndLimit.length == 2) { - tag = tagAndLimit[0]; - int limit = Integer.parseInt(tagAndLimit[1]); - if (limits.containsKey(tag) && limits.get(tag) != limit) { - throw new BadTagLimitException(tag, limits.get(tag), limit); - } - limits.put(tag, limit); - } - - if (negation) { - or.add(new Not(new TagExp(tag))); - } else { - or.add(new TagExp(tag)); - } - } - and.add(or); - } - - private interface Expression { - boolean eval(Collection tags); - } - - private class Not implements Expression { - private final Expression expression; - - Not(Expression expression) { - this.expression = expression; - } - - public boolean eval(Collection tags) { - return !expression.eval(tags); - } - } - - private class And implements Expression { - private List expressions = new ArrayList(); - - public void add(Expression expression) { - expressions.add(expression); - } - - public boolean eval(Collection tags) { - boolean result = true; - for (Expression expression : expressions) { - result = expression.eval(tags); - if (!result) break; - } - return result; - } - - public boolean isEmpty() { - return expressions.isEmpty(); - } - } - - private class Or implements Expression { - private List expressions = new ArrayList(); - - public void add(Expression expression) { - expressions.add(expression); - } - - public boolean eval(Collection tags) { - boolean result = false; - for (Expression expression : expressions) { - result = expression.eval(tags); - if (result) break; - } - return result; - } - } - - private class TagExp implements Expression { - private final String tagName; - - TagExp(String tagName) { - if (!tagName.startsWith("@")) { - throw new BadTagException(tagName); - } - this.tagName = tagName; - } - - public boolean eval(Collection tags) { - for (PickleTag tag : tags) { - if (tagName.equals(tag.getName())) { - return true; - } - } - return false; - } - } - - private class BadTagException extends RuntimeException { - BadTagException(String tagName) { - super("Bad tag: \"" + tagName + "\""); - } - } - - private class BadTagLimitException extends RuntimeException { - BadTagLimitException(String tag, int limitA, int limitB) { - super("Inconsistent tag limits for " + tag + ": " + limitA + " and " + limitB); - } - } -} diff --git a/core/src/main/java/cucumber/runtime/formatter/Format.java b/core/src/main/java/cucumber/runtime/formatter/Format.java deleted file mode 100644 index 4b60cf4392..0000000000 --- a/core/src/main/java/cucumber/runtime/formatter/Format.java +++ /dev/null @@ -1,5 +0,0 @@ -package cucumber.runtime.formatter; - -public interface Format { - String text(String text); -} diff --git a/core/src/main/java/cucumber/runtime/formatter/Formats.java b/core/src/main/java/cucumber/runtime/formatter/Formats.java deleted file mode 100644 index c803eab5a9..0000000000 --- a/core/src/main/java/cucumber/runtime/formatter/Formats.java +++ /dev/null @@ -1,7 +0,0 @@ -package cucumber.runtime.formatter; - -public interface Formats { - Format get(String key); - - String up(int n); -} diff --git a/core/src/main/java/cucumber/runtime/formatter/NullSummaryPrinter.java b/core/src/main/java/cucumber/runtime/formatter/NullSummaryPrinter.java deleted file mode 100644 index 856639ea02..0000000000 --- a/core/src/main/java/cucumber/runtime/formatter/NullSummaryPrinter.java +++ /dev/null @@ -1,12 +0,0 @@ -package cucumber.runtime.formatter; - -import cucumber.api.SummaryPrinter; - -class NullSummaryPrinter implements SummaryPrinter { - - @SuppressWarnings("WeakerAccess") // Used by PluginFactory - public NullSummaryPrinter(){ - - } - -} diff --git a/core/src/main/java/cucumber/runtime/snippets/Concatenator.java b/core/src/main/java/cucumber/runtime/snippets/Concatenator.java deleted file mode 100644 index d086e406b7..0000000000 --- a/core/src/main/java/cucumber/runtime/snippets/Concatenator.java +++ /dev/null @@ -1,5 +0,0 @@ -package cucumber.runtime.snippets; - -public interface Concatenator { - String concatenate(String[] words); -} diff --git a/core/src/main/java/cucumber/util/FixJava.java b/core/src/main/java/cucumber/util/FixJava.java deleted file mode 100644 index 0259fcaeac..0000000000 --- a/core/src/main/java/cucumber/util/FixJava.java +++ /dev/null @@ -1,48 +0,0 @@ -package cucumber.util; - -import java.io.IOException; -import java.io.Reader; -import java.util.ArrayList; -import java.util.List; - -public class FixJava { - - private FixJava() { - - } - - public static String join(List objects, String separator) { - StringBuilder sb = new StringBuilder(); - int i = 0; - for (Object s : objects) { - if (i != 0) sb.append(separator); - sb.append(s); - i++; - } - return sb.toString(); - } - - public static List map(List objects, Mapper mapper) { - List result = new ArrayList(objects.size()); - for (T o : objects) { - result.add(mapper.map(o)); - } - return result; - } - - public static String readReader(Reader in) throws RuntimeException { - try { - StringBuilder buffer = new StringBuilder(); - final char[] data = new char[0x10000]; - int read; - - while ((read = in.read(data, 0, data.length)) != -1) { - buffer.append(data, 0, read); - } - return buffer.toString(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - -} diff --git a/core/src/main/java/cucumber/util/Mapper.java b/core/src/main/java/cucumber/util/Mapper.java deleted file mode 100644 index 3e648e3994..0000000000 --- a/core/src/main/java/cucumber/util/Mapper.java +++ /dev/null @@ -1,5 +0,0 @@ -package cucumber.util; - -public interface Mapper { - R map(T o); -} diff --git a/core/src/main/java/io/cucumber/core/api/Scenario.java b/core/src/main/java/io/cucumber/core/api/Scenario.java index df0346d637..c177069655 100644 --- a/core/src/main/java/io/cucumber/core/api/Scenario.java +++ b/core/src/main/java/io/cucumber/core/api/Scenario.java @@ -39,7 +39,10 @@ public interface Scenario { * * @param data what to embed, for example an image. * @param mimeType what is the data? + * + * @deprecated use {@link Scenario#embed(byte[], String, String)} instead. */ + @Deprecated void embed(byte[] data, String mimeType); /** diff --git a/core/src/main/java/io/cucumber/core/api/TypeRegistryConfigurer.java b/core/src/main/java/io/cucumber/core/api/TypeRegistryConfigurer.java index 8d965b4df1..86a968e4e6 100644 --- a/core/src/main/java/io/cucumber/core/api/TypeRegistryConfigurer.java +++ b/core/src/main/java/io/cucumber/core/api/TypeRegistryConfigurer.java @@ -10,12 +10,15 @@ @API(status = API.Status.STABLE) public interface TypeRegistryConfigurer { /** - * @return The locale to use. + * @return The locale to use, or null when language from feature file should be used. */ - Locale locale(); + default Locale locale() { + return null; + } /** * Configures the type registry. + * * @param typeRegistry The new type registry. */ void configureTypeRegistry(TypeRegistry typeRegistry); diff --git a/core/src/main/java/io/cucumber/core/backend/Backend.java b/core/src/main/java/io/cucumber/core/backend/Backend.java new file mode 100644 index 0000000000..d00ced1933 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/backend/Backend.java @@ -0,0 +1,33 @@ +package io.cucumber.core.backend; + +import io.cucumber.core.snippets.Snippet; +import org.apiguardian.api.API; + +import java.net.URI; +import java.util.List; + +@API(status = API.Status.STABLE) +public interface Backend { + /** + * Invoked once before all features. This is where steps and hooks should be loaded. + * + * @param glue Glue that provides the steps to be executed. + * @param gluePaths The locations for the glue to be loaded. + */ + void loadGlue(Glue glue, List gluePaths); + + /** + * Invoked before a new scenario starts. Implementations should do any necessary + * setup of new, isolated state here. Additional scenario scoped step definitions + * can be loaded here. These step definitions should implement + * {@link io.cucumber.core.runner.ScenarioScoped} + */ + void buildWorld(); + + /** + * Invoked at the end of a scenario, after hooks + */ + void disposeWorld(); + + Snippet getSnippet(); +} diff --git a/core/src/main/java/io/cucumber/core/backend/BackendProviderService.java b/core/src/main/java/io/cucumber/core/backend/BackendProviderService.java new file mode 100644 index 0000000000..4bee6d632b --- /dev/null +++ b/core/src/main/java/io/cucumber/core/backend/BackendProviderService.java @@ -0,0 +1,11 @@ +package io.cucumber.core.backend; + +import io.cucumber.core.io.ResourceLoader; +import org.apiguardian.api.API; + +@API(status = API.Status.STABLE) +public interface BackendProviderService { + + Backend create(Lookup lookup, Container container, ResourceLoader resourceLoader); + +} diff --git a/core/src/main/java/io/cucumber/core/backend/Container.java b/core/src/main/java/io/cucumber/core/backend/Container.java new file mode 100644 index 0000000000..b8c68a8088 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/backend/Container.java @@ -0,0 +1,14 @@ +package io.cucumber.core.backend; + +import org.apiguardian.api.API; + +@API(status = API.Status.STABLE) +public interface Container { + /** + * Collects glue classes in the classpath. Called once on init. + * + * @param glueClass Glue class containing cucumber.api annotations (Before, Given, When, ...) + * @return true if steps and hook definitions in this class should be used, false if they should be ignored. + */ + boolean addClass(Class glueClass); +} diff --git a/core/src/main/java/io/cucumber/core/backend/DataTableTypeDefinition.java b/core/src/main/java/io/cucumber/core/backend/DataTableTypeDefinition.java new file mode 100644 index 0000000000..40ff965f08 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/backend/DataTableTypeDefinition.java @@ -0,0 +1,19 @@ +package io.cucumber.core.backend; + +import io.cucumber.datatable.DataTableType; +import org.apiguardian.api.API; + +@API(status = API.Status.STABLE) +public interface DataTableTypeDefinition { + + DataTableType dataTableType(); + + /** + * The source line where the data table type is defined. + * Example: com/example/app/Cucumber.test():42 + * + * @param detail true if extra detailed location information should be included. + * @return The source line of the step definition. + */ + String getLocation(boolean detail); +} diff --git a/core/src/main/java/io/cucumber/core/backend/DefaultDataTableCellTransformerDefinition.java b/core/src/main/java/io/cucumber/core/backend/DefaultDataTableCellTransformerDefinition.java new file mode 100644 index 0000000000..8c8ad6db91 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/backend/DefaultDataTableCellTransformerDefinition.java @@ -0,0 +1,20 @@ +package io.cucumber.core.backend; + +import io.cucumber.datatable.TableCellByTypeTransformer; +import org.apiguardian.api.API; + +@API(status = API.Status.STABLE) +public interface DefaultDataTableCellTransformerDefinition { + + TableCellByTypeTransformer tableCellByTypeTransformer(); + + /** + * The source line where the default data table cell is defined. + * Example: com/example/app/Cucumber.test():42 + * + * @param detail true if extra detailed location information should be included. + * @return The source line of the step definition. + */ + String getLocation(boolean detail); + +} diff --git a/core/src/main/java/io/cucumber/core/backend/DefaultDataTableEntryTransformerDefinition.java b/core/src/main/java/io/cucumber/core/backend/DefaultDataTableEntryTransformerDefinition.java new file mode 100644 index 0000000000..3db632f6f5 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/backend/DefaultDataTableEntryTransformerDefinition.java @@ -0,0 +1,19 @@ +package io.cucumber.core.backend; + +import io.cucumber.datatable.TableEntryByTypeTransformer; +import org.apiguardian.api.API; + +@API(status = API.Status.STABLE) +public interface DefaultDataTableEntryTransformerDefinition { + + TableEntryByTypeTransformer tableEntryByTypeTransformer(); + + /** + * The source line where the default table entry transformer is defined. + * Example: com/example/app/Cucumber.test():42 + * + * @param detail true if extra detailed location information should be included. + * @return The source line of the step definition. + */ + String getLocation(boolean detail); +} diff --git a/core/src/main/java/io/cucumber/core/backend/DefaultParameterTransformerDefinition.java b/core/src/main/java/io/cucumber/core/backend/DefaultParameterTransformerDefinition.java new file mode 100644 index 0000000000..14c0103d29 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/backend/DefaultParameterTransformerDefinition.java @@ -0,0 +1,19 @@ +package io.cucumber.core.backend; + +import io.cucumber.cucumberexpressions.ParameterByTypeTransformer; +import org.apiguardian.api.API; + +@API(status = API.Status.STABLE) +public interface DefaultParameterTransformerDefinition { + + ParameterByTypeTransformer parameterByTypeTransformer(); + + /** + * The source line where the default parameter transformer is defined. + * Example: com/example/app/Cucumber.test():42 + * + * @param detail true if extra detailed location information should be included. + * @return The source line of the step definition. + */ + String getLocation(boolean detail); +} diff --git a/core/src/main/java/io/cucumber/core/backend/Glue.java b/core/src/main/java/io/cucumber/core/backend/Glue.java new file mode 100644 index 0000000000..5da600fd72 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/backend/Glue.java @@ -0,0 +1,28 @@ +package io.cucumber.core.backend; + +import org.apiguardian.api.API; + +@API(status = API.Status.STABLE) +public interface Glue { + + void addStepDefinition(StepDefinition stepDefinition); + + void addBeforeHook(HookDefinition beforeHook); + + void addAfterHook(HookDefinition afterHook); + + void addBeforeStepHook(HookDefinition beforeStepHook); + + void addAfterStepHook(HookDefinition afterStepHook); + + void addParameterType(ParameterTypeDefinition parameterTypeDefinition); + + void addDataTableType(DataTableTypeDefinition dataTableTypeDefinition); + + void addDefaultParameterTransformer(DefaultParameterTransformerDefinition defaultParameterTransformer); + + void addDefaultDataTableEntryTransformer(DefaultDataTableEntryTransformerDefinition defaultDataTableEntryTransformer); + + void addDefaultDataTableCellTransformer(DefaultDataTableCellTransformerDefinition defaultDataTableCellTransformer); + +} diff --git a/core/src/main/java/cucumber/runtime/HookDefinition.java b/core/src/main/java/io/cucumber/core/backend/HookDefinition.java similarity index 61% rename from core/src/main/java/cucumber/runtime/HookDefinition.java rename to core/src/main/java/io/cucumber/core/backend/HookDefinition.java index 4e6596cc1b..9ff38ed372 100644 --- a/core/src/main/java/cucumber/runtime/HookDefinition.java +++ b/core/src/main/java/io/cucumber/core/backend/HookDefinition.java @@ -1,16 +1,19 @@ -package cucumber.runtime; +package io.cucumber.core.backend; -import cucumber.api.Scenario; +import io.cucumber.core.api.Scenario; import gherkin.pickles.PickleTag; +import org.apiguardian.api.API; import java.util.Collection; +@API(status = API.Status.STABLE) public interface HookDefinition { /** * The source line where the step definition is defined. * Example: foo/bar/Zap.brainfuck:42 * * @param detail true if extra detailed location information should be included. + * @return The source line where the step definition is defined. */ String getLocation(boolean detail); @@ -19,11 +22,4 @@ public interface HookDefinition { boolean matches(Collection tags); int getOrder(); - - /** - * @deprecated replaced with {@link ScenarioScoped} - * @return true if this instance is scoped to a single scenario, or false if it can be reused across scenarios. - */ - @Deprecated - boolean isScenarioScoped(); } diff --git a/core/src/main/java/io/cucumber/core/backend/Lookup.java b/core/src/main/java/io/cucumber/core/backend/Lookup.java new file mode 100644 index 0000000000..9bf5037ea6 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/backend/Lookup.java @@ -0,0 +1,15 @@ +package io.cucumber.core.backend; + +import org.apiguardian.api.API; + +@API(status = API.Status.STABLE) +public interface Lookup { + /** + * Provides the glue instances used to execute the current scenario. + * + * @param glueClass type of instance to be created. + * @param type of Glue class + * @return new Glue instance of type T + */ + T getInstance(Class glueClass); +} diff --git a/core/src/main/java/io/cucumber/core/backend/ObjectFactory.java b/core/src/main/java/io/cucumber/core/backend/ObjectFactory.java index fb05be88a4..4272a0705c 100644 --- a/core/src/main/java/io/cucumber/core/backend/ObjectFactory.java +++ b/core/src/main/java/io/cucumber/core/backend/ObjectFactory.java @@ -6,38 +6,16 @@ * Minimal facade for Dependency Injection containers */ @API(status = API.Status.STABLE) -public interface ObjectFactory { +public interface ObjectFactory extends Container, Lookup { /** - * Instantiate glue code before scenario execution. Called once per - * scenario. + * Instantiate glue code before scenario execution. Called once per scenario. */ void start(); /** - * Dispose glue code after scenario execution. Called once per - * scenario. + * Dispose glue code after scenario execution. Called once per scenario. */ void stop(); - /** - * Collects glue classes in the classpath. Called once on init. - * - * @param glueClass Glue class containing cucumber.api annotations (Before, - * Given, When, ...) - * @return true if stepdefs and hooks in this class should be used, false if - * they should be ignored. - */ - boolean addClass(Class glueClass); - - /** - * Provides the glue instances used to execute the current scenario. The - * instance can be prepared in {@link #start()}. - * - * @param glueClass type of instance to be created. - * @param type of Glue class - * @return new Glue instance of type T - */ - T getInstance(Class glueClass); - } diff --git a/core/src/main/java/io/cucumber/core/backend/ObjectFactoryServiceLoader.java b/core/src/main/java/io/cucumber/core/backend/ObjectFactoryServiceLoader.java new file mode 100644 index 0000000000..aaa8259d75 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/backend/ObjectFactoryServiceLoader.java @@ -0,0 +1,117 @@ +package io.cucumber.core.backend; + +import io.cucumber.core.exception.CucumberException; + +import java.lang.reflect.Constructor; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.ServiceLoader; + +import static java.util.Objects.requireNonNull; + +public final class ObjectFactoryServiceLoader { + + private final Options options; + + public ObjectFactoryServiceLoader(Options options) { + this.options = requireNonNull(options); + } + + /** + * Loads an instance of {@link ObjectFactory} using the {@link ServiceLoader} mechanism. + *

+ * Will load an instance of the class provided by {@link Options#getObjectFactoryClass()}. + *

+ * If {@link Options#getObjectFactoryClass()} does not provide a class and there is exactly + * one {@code ObjectFactory} instance available that instance will be used. + *

+ * Otherwise a default object factory with no dependency injection capabilities will be used. + * + * @return an instance of {@link ObjectFactory} + */ + public ObjectFactory loadObjectFactory() { + Class objectFactoryClass = this.options.getObjectFactoryClass(); + + final ServiceLoader loader = ServiceLoader.load(ObjectFactory.class); + if (objectFactoryClass == null) { + return loadSingleObjectFactoryOrDefault(loader); + + } + + return loadSelectedObjectFactory(loader, objectFactoryClass); + } + + private static ObjectFactory loadSelectedObjectFactory(ServiceLoader loader, Class objectFactoryClass) { + for (ObjectFactory objectFactory : loader) { + if (objectFactoryClass.equals(objectFactory.getClass())) { + return objectFactory; + } + } + + throw new CucumberException("Could not find object factory " + objectFactoryClass); + } + + private static ObjectFactory loadSingleObjectFactoryOrDefault(ServiceLoader loader) { + final Iterator objectFactories = loader.iterator(); + + ObjectFactory objectFactory; + if (objectFactories.hasNext()) { + objectFactory = objectFactories.next(); + } else { + objectFactory = new DefaultJavaObjectFactory(); + } + + if (objectFactories.hasNext()) { + System.out.println(getMultipleObjectFactoryLogMessage()); + objectFactory = new DefaultJavaObjectFactory(); + } + return objectFactory; + } + + private static String getMultipleObjectFactoryLogMessage() { + return "More than one Cucumber ObjectFactory was found in the classpath\n" + + "\n" + + "You probably may have included, for instance, cucumber-spring AND cucumber-guice as part of\n" + + "your dependencies. When this happens, Cucumber falls back to instantiating the\n" + + "DefaultJavaObjectFactory implementation which doesn't provide IoC.\n" + + "In order to enjoy IoC features, please remove the unnecessary dependencies from your classpath.\n"; + } + + static class DefaultJavaObjectFactory implements ObjectFactory { + private final Map, Object> instances = new HashMap<>(); + + public void start() { + // No-op + } + + public void stop() { + instances.clear(); + } + + public boolean addClass(Class clazz) { + return true; + } + + public T getInstance(Class type) { + T instance = type.cast(instances.get(type)); + if (instance == null) { + instance = cacheNewInstance(type); + } + return instance; + } + + private T cacheNewInstance(Class type) { + try { + Constructor constructor = type.getConstructor(); + T instance = constructor.newInstance(); + instances.put(type, instance); + return instance; + } catch (NoSuchMethodException e) { + throw new CucumberException(String.format("%s doesn't have an empty constructor. If you need DI, put cucumber-picocontainer on the classpath", type), e); + } catch (Exception e) { + throw new CucumberException(String.format("Failed to instantiate %s", type), e); + } + } + } +} diff --git a/core/src/main/java/io/cucumber/core/backend/Options.java b/core/src/main/java/io/cucumber/core/backend/Options.java new file mode 100644 index 0000000000..159d60a833 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/backend/Options.java @@ -0,0 +1,6 @@ +package io.cucumber.core.backend; + +public interface Options { + + Class getObjectFactoryClass(); +} diff --git a/core/src/main/java/io/cucumber/core/backend/ParameterInfo.java b/core/src/main/java/io/cucumber/core/backend/ParameterInfo.java new file mode 100644 index 0000000000..99c734edf6 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/backend/ParameterInfo.java @@ -0,0 +1,12 @@ +package io.cucumber.core.backend; + +import java.lang.reflect.Type; + +public interface ParameterInfo { + + Type getType(); + + boolean isTransposed(); + + TypeResolver getTypeResolver(); +} diff --git a/core/src/main/java/io/cucumber/core/backend/ParameterTypeDefinition.java b/core/src/main/java/io/cucumber/core/backend/ParameterTypeDefinition.java new file mode 100644 index 0000000000..e0956cbcb4 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/backend/ParameterTypeDefinition.java @@ -0,0 +1,19 @@ +package io.cucumber.core.backend; + +import io.cucumber.cucumberexpressions.ParameterType; +import org.apiguardian.api.API; + +@API(status = API.Status.EXPERIMENTAL) +public interface ParameterTypeDefinition { + + ParameterType parameterType(); + + /** + * The source line where the parameter type is defined. + * Example: com/example/app/Cucumber.test():42 + * + * @param detail true if extra detailed location information should be included. + * @return The source line of the step definition. + */ + String getLocation(boolean detail); +} diff --git a/core/src/main/java/cucumber/api/Pending.java b/core/src/main/java/io/cucumber/core/backend/Pending.java similarity index 82% rename from core/src/main/java/cucumber/api/Pending.java rename to core/src/main/java/io/cucumber/core/backend/Pending.java index c8460c5298..4d864857b4 100644 --- a/core/src/main/java/cucumber/api/Pending.java +++ b/core/src/main/java/io/cucumber/core/backend/Pending.java @@ -1,4 +1,7 @@ -package cucumber.api; +package io.cucumber.core.backend; + + +import org.apiguardian.api.API; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -9,9 +12,8 @@ * Any exception class annotated with this annotation will be treated as a "pending" exception. * That is - if the exception is thrown from a step definition or hook, the scenario's status will * be pending instead of failed. - * - * @see PendingException */ +@API(status = API.Status.STABLE) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Pending { diff --git a/core/src/main/java/io/cucumber/core/backend/StepDefinition.java b/core/src/main/java/io/cucumber/core/backend/StepDefinition.java new file mode 100644 index 0000000000..bbeac8cba0 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/backend/StepDefinition.java @@ -0,0 +1,30 @@ +package io.cucumber.core.backend; + +import org.apiguardian.api.API; + +import java.util.List; + +@API(status = API.Status.STABLE) +public interface StepDefinition extends io.cucumber.core.event.StepDefinition { + /** + * Invokes the step definition. The method should raise a Throwable + * if the invocation fails, which will cause the step to fail. + * + * @param args The arguments for the step + * @throws Throwable in case of step failure. + */ + void execute(Object[] args) throws Throwable; + + /** + * @param stackTraceElement The location of the step. + * @return Return true if this matches the location. This is used to filter + * stack traces. + */ + boolean isDefinedAt(StackTraceElement stackTraceElement); + + /** + * @return parameter information or null when the language does not provide parameter information + */ + List parameterInfos(); + +} diff --git a/core/src/main/java/io/cucumber/core/backend/TypeResolver.java b/core/src/main/java/io/cucumber/core/backend/TypeResolver.java new file mode 100644 index 0000000000..5e3199a6eb --- /dev/null +++ b/core/src/main/java/io/cucumber/core/backend/TypeResolver.java @@ -0,0 +1,22 @@ +package io.cucumber.core.backend; + +import org.apiguardian.api.API; + +import java.lang.reflect.Type; + +/** + * Allows lazy resolution of the type of a data table or doc string. + */ +@API(status = API.Status.STABLE) +public interface TypeResolver { + + /** + * A type to data convert the table or doc string to. May not return null. + *

+ * When the {@link Object} type is returned no transform will be applied. + * + * @return a type + */ + Type resolve(); + +} diff --git a/core/src/main/java/io/cucumber/core/cli/Main.java b/core/src/main/java/io/cucumber/core/cli/Main.java index 87ffb002f1..c03b3a9985 100644 --- a/core/src/main/java/io/cucumber/core/cli/Main.java +++ b/core/src/main/java/io/cucumber/core/cli/Main.java @@ -1,13 +1,27 @@ package io.cucumber.core.cli; -import cucumber.runtime.Env; -import cucumber.runtime.Runtime; -import cucumber.runtime.io.MultiLoader; -import cucumber.runtime.io.ResourceLoader; +import io.cucumber.core.io.MultiLoader; +import io.cucumber.core.io.ResourceLoader; import io.cucumber.core.options.CommandlineOptionsParser; -import io.cucumber.core.options.EnvironmentOptionsParser; +import io.cucumber.core.options.Constants; +import io.cucumber.core.options.CucumberProperties; +import io.cucumber.core.options.CucumberPropertiesParser; import io.cucumber.core.options.RuntimeOptions; +import io.cucumber.core.runtime.Runtime; +import org.apiguardian.api.API; +/** + * Cucumber Main. Runs Cucumber as a CLI. + *

+ * Options can be provided in order of precedence through: + *

    + *
  1. command line arguments
  2. + *
  3. {@value Constants#CUCUMBER_OPTIONS_PROPERTY_NAME} property in {@link System#getProperties()}
  4. + *
  5. {@value Constants#CUCUMBER_OPTIONS_PROPERTY_NAME} property in {@link System#getenv()}
  6. + *
  7. {@value Constants#CUCUMBER_OPTIONS_PROPERTY_NAME} property in {@value Constants#CUCUMBER_PROPERTIES_FILE_NAME}
  8. + *
+ */ +@API(status = API.Status.STABLE) public class Main { public static void main(String[] argv) { @@ -25,15 +39,22 @@ public static void main(String[] argv) { public static byte run(String[] argv, ClassLoader classLoader) { ResourceLoader resourceLoader = new MultiLoader(classLoader); - RuntimeOptions runtimeOptions = new CommandlineOptionsParser(resourceLoader) - .parse(argv) - .addDefaultFormatterIfNotPresent() - .addDefaultSummaryPrinterIfNotPresent() + RuntimeOptions propertiesFileOptions = new CucumberPropertiesParser(resourceLoader) + .parse(CucumberProperties.fromPropertiesFile()) .build(); - new EnvironmentOptionsParser(resourceLoader) - .parse(Env.INSTANCE) - .build(runtimeOptions); + RuntimeOptions environmentOptions = new CucumberPropertiesParser(resourceLoader) + .parse(CucumberProperties.fromEnvironment()) + .build(propertiesFileOptions); + + RuntimeOptions systemOptions = new CucumberPropertiesParser(resourceLoader) + .parse(CucumberProperties.fromSystemProperties()) + .build(environmentOptions); + + RuntimeOptions runtimeOptions = new CommandlineOptionsParser() + .parse(argv) + .build(systemOptions); + final Runtime runtime = Runtime.builder() .withRuntimeOptions(runtimeOptions) diff --git a/core/src/main/java/cucumber/api/Argument.java b/core/src/main/java/io/cucumber/core/event/Argument.java similarity index 79% rename from core/src/main/java/cucumber/api/Argument.java rename to core/src/main/java/io/cucumber/core/event/Argument.java index 13906ace1f..6a71b74b79 100644 --- a/core/src/main/java/cucumber/api/Argument.java +++ b/core/src/main/java/io/cucumber/core/event/Argument.java @@ -1,4 +1,6 @@ -package cucumber.api; +package io.cucumber.core.event; + +import org.apiguardian.api.API; /** * Represents an argument in for a step definition. @@ -8,6 +10,7 @@ * one argument with value {@code "4"}, starting at {@code 7} and * ending at {@code 8}. */ +@API(status = API.Status.STABLE) public interface Argument { String getValue(); diff --git a/core/src/main/java/io/cucumber/core/event/EmbedEvent.java b/core/src/main/java/io/cucumber/core/event/EmbedEvent.java new file mode 100644 index 0000000000..0dfd4e1d3d --- /dev/null +++ b/core/src/main/java/io/cucumber/core/event/EmbedEvent.java @@ -0,0 +1,39 @@ +package io.cucumber.core.event; + +import org.apiguardian.api.API; + +import java.time.Instant; +import java.util.Objects; + +@API(status = API.Status.STABLE) +public final class EmbedEvent extends TestCaseEvent { + private final byte[] data; + private final String mimeType; + public final String name; + + public EmbedEvent(Instant timeInstant, TestCase testCase, byte[] data, String mimeType) { + super(timeInstant, testCase); + this.data = Objects.requireNonNull(data); + this.mimeType = Objects.requireNonNull(mimeType); + this.name = null; + } + + public EmbedEvent(Instant timeInstant, TestCase testCase, byte[] data, String mimeType, String name) { + super(timeInstant, testCase); + this.data = data; + this.mimeType = mimeType; + this.name = name; + } + + public byte[] getData() { + return data; + } + + public String getMimeType() { + return mimeType; + } + + public String getName() { + return name; + } +} diff --git a/core/src/main/java/io/cucumber/core/event/Event.java b/core/src/main/java/io/cucumber/core/event/Event.java new file mode 100644 index 0000000000..ed44e4b614 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/event/Event.java @@ -0,0 +1,17 @@ +package io.cucumber.core.event; + +import org.apiguardian.api.API; + +import java.time.Instant; + +@API(status = API.Status.STABLE) +public interface Event { + + /** + * Returns instant from epoch. + * + * @return time instant in Instant + * @see Instant#now() + */ + Instant getInstant(); +} diff --git a/core/src/main/java/io/cucumber/core/event/EventHandler.java b/core/src/main/java/io/cucumber/core/event/EventHandler.java new file mode 100644 index 0000000000..d9a6d15d66 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/event/EventHandler.java @@ -0,0 +1,10 @@ +package io.cucumber.core.event; + +import org.apiguardian.api.API; + +@API(status = API.Status.STABLE) +public interface EventHandler { + + void receive(T event); + +} diff --git a/core/src/main/java/cucumber/api/event/EventPublisher.java b/core/src/main/java/io/cucumber/core/event/EventPublisher.java similarity index 95% rename from core/src/main/java/cucumber/api/event/EventPublisher.java rename to core/src/main/java/io/cucumber/core/event/EventPublisher.java index 742babdd75..00aca48ecd 100644 --- a/core/src/main/java/cucumber/api/event/EventPublisher.java +++ b/core/src/main/java/io/cucumber/core/event/EventPublisher.java @@ -1,5 +1,8 @@ -package cucumber.api.event; +package io.cucumber.core.event; +import org.apiguardian.api.API; + +@API(status = API.Status.STABLE) public interface EventPublisher { /** diff --git a/core/src/main/java/cucumber/api/HookTestStep.java b/core/src/main/java/io/cucumber/core/event/HookTestStep.java similarity index 66% rename from core/src/main/java/cucumber/api/HookTestStep.java rename to core/src/main/java/io/cucumber/core/event/HookTestStep.java index c014c498ac..64416652b3 100644 --- a/core/src/main/java/cucumber/api/HookTestStep.java +++ b/core/src/main/java/io/cucumber/core/event/HookTestStep.java @@ -1,12 +1,15 @@ -package cucumber.api; +package io.cucumber.core.event; + +import org.apiguardian.api.API; /** * Hooks are invoked before and after each scenario and before and * after each gherkin step in a scenario. * - * @see cucumber.api.event.TestCaseStarted - * @see cucumber.api.event.TestCaseFinished + * @see TestCaseStarted + * @see TestCaseFinished */ +@API(status = API.Status.STABLE) public interface HookTestStep extends TestStep { /** diff --git a/core/src/main/java/io/cucumber/core/event/HookType.java b/core/src/main/java/io/cucumber/core/event/HookType.java new file mode 100644 index 0000000000..70d8f28865 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/event/HookType.java @@ -0,0 +1,8 @@ +package io.cucumber.core.event; + +import org.apiguardian.api.API; + +@API(status = API.Status.STABLE) +public enum HookType { + BEFORE, AFTER, BEFORE_STEP, AFTER_STEP; +} diff --git a/core/src/main/java/cucumber/api/PickleStepTestStep.java b/core/src/main/java/io/cucumber/core/event/PickleStepTestStep.java similarity index 93% rename from core/src/main/java/cucumber/api/PickleStepTestStep.java rename to core/src/main/java/io/cucumber/core/event/PickleStepTestStep.java index 7e63f8b797..485e459586 100644 --- a/core/src/main/java/cucumber/api/PickleStepTestStep.java +++ b/core/src/main/java/io/cucumber/core/event/PickleStepTestStep.java @@ -1,10 +1,13 @@ -package cucumber.api; +package io.cucumber.core.event; + +import org.apiguardian.api.API; import java.util.List; /** * A pickle test step matches a line in a Gherkin scenario or background. */ +@API(status = API.Status.STABLE) public interface PickleStepTestStep extends TestStep { /** diff --git a/core/src/main/java/io/cucumber/core/event/Result.java b/core/src/main/java/io/cucumber/core/event/Result.java new file mode 100644 index 0000000000..408bbe768a --- /dev/null +++ b/core/src/main/java/io/cucumber/core/event/Result.java @@ -0,0 +1,65 @@ +package io.cucumber.core.event; + +import org.apiguardian.api.API; + +import java.time.Duration; +import java.util.Objects; + +import static java.util.Objects.requireNonNull; + +@API(status = API.Status.STABLE) +public final class Result{ + + private final Status status; + private final Duration duration; + private final Throwable error; + + /** + * The result of a step or scenario + * + * @param status status of the step or scenario + * @param duration the duration + * @param error the error that caused the failure if any + */ + public Result(Status status, Duration duration, Throwable error) { + this.status = requireNonNull(status); + this.duration = requireNonNull(duration); + this.error = error; + } + + public Status getStatus() { + return status; + } + + public Duration getDuration() { + return duration; + } + + public Throwable getError() { + return error; + } + + @Override + public String toString() { + return "Result{" + + "status=" + status + + ", duration=" + duration.getSeconds() + + ", error=" + error + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Result result = (Result) o; + return status == result.status && + Objects.equals(duration, result.duration) && + Objects.equals(error, result.error); + } + + @Override + public int hashCode() { + return Objects.hash(status, duration, error); + } +} diff --git a/core/src/main/java/io/cucumber/core/event/SnippetsSuggestedEvent.java b/core/src/main/java/io/cucumber/core/event/SnippetsSuggestedEvent.java new file mode 100644 index 0000000000..c881e0b972 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/event/SnippetsSuggestedEvent.java @@ -0,0 +1,53 @@ +package io.cucumber.core.event; + +import org.apiguardian.api.API; + +import java.time.Instant; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +@API(status = API.Status.STABLE) +public final class SnippetsSuggestedEvent extends TimeStampedEvent { + private final String uri; + private final List stepLocations; + private final List snippets; + + public SnippetsSuggestedEvent(Instant timeInstant, String uri, List stepLocations, List snippets) { + super(timeInstant); + this.uri = Objects.requireNonNull(uri); + this.stepLocations = Objects.requireNonNull(stepLocations); + this.snippets = Collections.unmodifiableList(Objects.requireNonNull(snippets)); + } + + public String getUri() { + return uri; + } + + public List getStepLocations() { + return stepLocations; + } + + public List getSnippets() { + return snippets; + } + + public static final class Location { + private final int line; + private final int column; + + public Location(int line, int column) { + this.line = line; + this.column = column; + } + + public int getLine() { + return line; + } + + public int getColumn() { + return column; + } + } + +} diff --git a/core/src/main/java/io/cucumber/core/event/Status.java b/core/src/main/java/io/cucumber/core/event/Status.java index 94d6c2432f..ea9cd72a60 100644 --- a/core/src/main/java/io/cucumber/core/event/Status.java +++ b/core/src/main/java/io/cucumber/core/event/Status.java @@ -1,5 +1,8 @@ package io.cucumber.core.event; +import org.apiguardian.api.API; + +@API(status = API.Status.STABLE) public enum Status { PASSED, SKIPPED, diff --git a/core/src/main/java/io/cucumber/core/event/StepDefinedEvent.java b/core/src/main/java/io/cucumber/core/event/StepDefinedEvent.java new file mode 100644 index 0000000000..54c5f4dc1d --- /dev/null +++ b/core/src/main/java/io/cucumber/core/event/StepDefinedEvent.java @@ -0,0 +1,21 @@ +package io.cucumber.core.event; + + +import org.apiguardian.api.API; + +import java.time.Instant; +import java.util.Objects; + +@API(status = API.Status.STABLE) +public final class StepDefinedEvent extends TimeStampedEvent { + private final StepDefinition stepDefinition; + + public StepDefinedEvent(Instant timeInstant, StepDefinition stepDefinition) { + super(timeInstant); + this.stepDefinition = Objects.requireNonNull(stepDefinition); + } + + public StepDefinition getStepDefinition() { + return stepDefinition; + } +} diff --git a/core/src/main/java/io/cucumber/core/event/StepDefinition.java b/core/src/main/java/io/cucumber/core/event/StepDefinition.java new file mode 100644 index 0000000000..3f6353a619 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/event/StepDefinition.java @@ -0,0 +1,22 @@ +package io.cucumber.core.event; + + +import org.apiguardian.api.API; + +@API(status = API.Status.STABLE) +public interface StepDefinition { + + /** + * The source line where the step definition is defined. + * Example: com/example/app/Cucumber.test():42 + * + * @param detail true if extra detailed location information should be included. + * @return The source line of the step definition. + */ + String getLocation(boolean detail); + + /** + * @return the pattern associated with this instance. Used for error reporting only. + */ + String getPattern(); +} diff --git a/core/src/main/java/io/cucumber/core/event/TestCase.java b/core/src/main/java/io/cucumber/core/event/TestCase.java new file mode 100644 index 0000000000..eb3a65821e --- /dev/null +++ b/core/src/main/java/io/cucumber/core/event/TestCase.java @@ -0,0 +1,26 @@ +package io.cucumber.core.event; + +import org.apiguardian.api.API; + +import java.util.List; + +@API(status = API.Status.STABLE) +public interface TestCase { + + /** + * @return the line in the feature file of the Scenario. If this is a Scenario + * from Scenario Outlines this wil return the line of the example row in + * the Scenario Outline. + */ + Integer getLine(); + + String getName(); + + String getScenarioDesignation(); + + List getTags(); + + List getTestSteps(); + + String getUri(); +} diff --git a/core/src/main/java/io/cucumber/core/event/TestCaseEvent.java b/core/src/main/java/io/cucumber/core/event/TestCaseEvent.java new file mode 100644 index 0000000000..df8fe28054 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/event/TestCaseEvent.java @@ -0,0 +1,21 @@ +package io.cucumber.core.event; + +import org.apiguardian.api.API; + +import java.time.Instant; +import java.util.Objects; + +@API(status = API.Status.STABLE) +public abstract class TestCaseEvent extends TimeStampedEvent { + + private final TestCase testCase; + + TestCaseEvent(Instant timeInstant, TestCase testCase) { + super(timeInstant); + this.testCase = Objects.requireNonNull(testCase); + } + + public TestCase getTestCase() { + return testCase; + } +} diff --git a/core/src/main/java/io/cucumber/core/event/TestCaseFinished.java b/core/src/main/java/io/cucumber/core/event/TestCaseFinished.java new file mode 100644 index 0000000000..07b4c65a20 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/event/TestCaseFinished.java @@ -0,0 +1,27 @@ +package io.cucumber.core.event; + +import org.apiguardian.api.API; + +import java.time.Instant; +import java.util.Objects; + +@API(status = API.Status.STABLE) +public final class TestCaseFinished extends TestCaseEvent { + private final Result result; + private final TestCase testCase; + + public TestCaseFinished(Instant timeInstant, TestCase testCase, Result result) { + super(timeInstant, testCase); + this.testCase = Objects.requireNonNull(testCase); + this.result = Objects.requireNonNull(result); + } + + public Result getResult() { + return result; + } + + @Override + public TestCase getTestCase() { + return testCase; + } +} diff --git a/core/src/main/java/io/cucumber/core/event/TestCaseStarted.java b/core/src/main/java/io/cucumber/core/event/TestCaseStarted.java new file mode 100644 index 0000000000..c6c5b28ee0 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/event/TestCaseStarted.java @@ -0,0 +1,21 @@ +package io.cucumber.core.event; + +import org.apiguardian.api.API; + +import java.time.Instant; +import java.util.Objects; + +@API(status = API.Status.STABLE) +public final class TestCaseStarted extends TestCaseEvent { + private final TestCase testCase; + + public TestCaseStarted(Instant timeInstant, TestCase testCase) { + super(timeInstant, testCase); + this.testCase = Objects.requireNonNull(testCase); + } + + @Override + public TestCase getTestCase() { + return testCase; + } +} diff --git a/core/src/main/java/io/cucumber/core/event/TestRunFinished.java b/core/src/main/java/io/cucumber/core/event/TestRunFinished.java new file mode 100644 index 0000000000..14e6c02985 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/event/TestRunFinished.java @@ -0,0 +1,13 @@ +package io.cucumber.core.event; + +import org.apiguardian.api.API; + +import java.time.Instant; + +@API(status = API.Status.STABLE) +public final class TestRunFinished extends TimeStampedEvent { + + public TestRunFinished(Instant timeInstant) { + super(timeInstant); + } +} diff --git a/core/src/main/java/io/cucumber/core/event/TestRunStarted.java b/core/src/main/java/io/cucumber/core/event/TestRunStarted.java new file mode 100644 index 0000000000..ab7ce723af --- /dev/null +++ b/core/src/main/java/io/cucumber/core/event/TestRunStarted.java @@ -0,0 +1,13 @@ +package io.cucumber.core.event; + +import org.apiguardian.api.API; + +import java.time.Instant; + +@API(status = API.Status.STABLE) +public final class TestRunStarted extends TimeStampedEvent { + + public TestRunStarted(Instant timeInstant) { + super(timeInstant); + } +} diff --git a/core/src/main/java/io/cucumber/core/event/TestSourceRead.java b/core/src/main/java/io/cucumber/core/event/TestSourceRead.java new file mode 100644 index 0000000000..7df4734922 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/event/TestSourceRead.java @@ -0,0 +1,26 @@ +package io.cucumber.core.event; + +import org.apiguardian.api.API; + +import java.time.Instant; +import java.util.Objects; + +@API(status = API.Status.STABLE) +public final class TestSourceRead extends TimeStampedEvent { + private final String uri; + private final String source; + + public TestSourceRead(Instant timeInstant, String uri, String source) { + super(timeInstant); + this.uri = Objects.requireNonNull(uri); + this.source = Objects.requireNonNull(source); + } + + public String getSource() { + return source; + } + + public String getUri() { + return uri; + } +} diff --git a/core/src/main/java/cucumber/api/TestStep.java b/core/src/main/java/io/cucumber/core/event/TestStep.java similarity index 70% rename from core/src/main/java/cucumber/api/TestStep.java rename to core/src/main/java/io/cucumber/core/event/TestStep.java index fcd3eb05e1..fdc9bff095 100644 --- a/core/src/main/java/cucumber/api/TestStep.java +++ b/core/src/main/java/io/cucumber/core/event/TestStep.java @@ -1,12 +1,16 @@ -package cucumber.api; +package io.cucumber.core.event; + +import org.apiguardian.api.API; /** * A test step can either represent the execution of a hook * or a pickle step. Each step is tied to some glue code. * - * @see cucumber.api.event.TestCaseStarted - * @see cucumber.api.event.TestCaseFinished + * @see TestCaseStarted + * @see TestCaseFinished */ + +@API(status = API.Status.STABLE) public interface TestStep { /** diff --git a/core/src/main/java/io/cucumber/core/event/TestStepFinished.java b/core/src/main/java/io/cucumber/core/event/TestStepFinished.java new file mode 100644 index 0000000000..13422104e1 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/event/TestStepFinished.java @@ -0,0 +1,43 @@ +package io.cucumber.core.event; + +import org.apiguardian.api.API; + +import java.time.Instant; +import java.util.Objects; + +/** + * A test step finished event is broadcast when ever a step finishes. + *

+ * A step can either be a {@link PickleStepTestStep} or a + * {@link HookTestStep} depending on what step was executed. + *

+ * Each test step finished event is followed by an matching + * {@link TestStepStarted} event for the same step.The order in which + * these events may be expected is: + *

+ *     [before hook,]* [[before step hook,]* test step, [after step hook,]*]+, [after hook,]*
+ * 
+ * + * @see PickleStepTestStep + * @see HookTestStep + */ + +@API(status = API.Status.STABLE) +public final class TestStepFinished extends TestCaseEvent { + private final TestStep testStep; + private final Result result; + + public TestStepFinished(Instant timeInstant, TestCase testCase, TestStep testStep, Result result) { + super(timeInstant, testCase); + this.testStep = Objects.requireNonNull(testStep); + this.result = Objects.requireNonNull(result); + } + + public Result getResult() { + return result; + } + + public TestStep getTestStep() { + return testStep; + } +} diff --git a/core/src/main/java/cucumber/api/event/TestStepStarted.java b/core/src/main/java/io/cucumber/core/event/TestStepStarted.java similarity index 53% rename from core/src/main/java/cucumber/api/event/TestStepStarted.java rename to core/src/main/java/io/cucumber/core/event/TestStepStarted.java index 6392eec7b2..c6d47f0ce9 100644 --- a/core/src/main/java/cucumber/api/event/TestStepStarted.java +++ b/core/src/main/java/io/cucumber/core/event/TestStepStarted.java @@ -1,9 +1,9 @@ -package cucumber.api.event; +package io.cucumber.core.event; -import cucumber.api.HookTestStep; -import cucumber.api.PickleStepTestStep; -import cucumber.api.TestCase; -import cucumber.api.TestStep; +import org.apiguardian.api.API; + +import java.time.Instant; +import java.util.Objects; /** * A test step started event is broadcast when ever a step starts. @@ -22,17 +22,17 @@ * @see PickleStepTestStep * @see HookTestStep */ + +@API(status = API.Status.STABLE) public final class TestStepStarted extends TestCaseEvent { - public final TestStep testStep; + private final TestStep testStep; - @Deprecated - public TestStepStarted(Long timeStamp, TestCase testCase, TestStep testStep) { - this(timeStamp, 0, testCase, testStep); + public TestStepStarted(Instant timeInstant, TestCase testCase, TestStep testStep) { + super(timeInstant, testCase); + this.testStep = Objects.requireNonNull(testStep); } - public TestStepStarted(Long timeStamp, long timeStampMillis, TestCase testCase, TestStep testStep) { - super(timeStamp, timeStampMillis, testCase); - this.testStep = testStep; + public TestStep getTestStep() { + return testStep; } - } diff --git a/core/src/main/java/io/cucumber/core/event/TimeStampedEvent.java b/core/src/main/java/io/cucumber/core/event/TimeStampedEvent.java new file mode 100644 index 0000000000..b51fc8b9dc --- /dev/null +++ b/core/src/main/java/io/cucumber/core/event/TimeStampedEvent.java @@ -0,0 +1,22 @@ +package io.cucumber.core.event; + +import java.time.Instant; +import java.util.Objects; + + +abstract class TimeStampedEvent implements Event { + + private final Instant instant; + + TimeStampedEvent(Instant timeInstant) { + this.instant = Objects.requireNonNull(timeInstant); + } + + /** + * {@inheritDoc} + */ + @Override + public Instant getInstant() { + return instant; + } +} diff --git a/core/src/main/java/io/cucumber/core/event/WriteEvent.java b/core/src/main/java/io/cucumber/core/event/WriteEvent.java new file mode 100644 index 0000000000..e3fbab800d --- /dev/null +++ b/core/src/main/java/io/cucumber/core/event/WriteEvent.java @@ -0,0 +1,20 @@ +package io.cucumber.core.event; + +import org.apiguardian.api.API; + +import java.time.Instant; +import java.util.Objects; + +@API(status = API.Status.STABLE) +public final class WriteEvent extends TestCaseEvent { + private final String text; + + public WriteEvent(Instant timeInstant, TestCase testCase, String text) { + super(timeInstant, testCase); + this.text = Objects.requireNonNull(text); + } + + public String getText() { + return text; + } +} diff --git a/core/src/main/java/cucumber/runner/AbstractEventBus.java b/core/src/main/java/io/cucumber/core/eventbus/AbstractEventBus.java similarity index 53% rename from core/src/main/java/cucumber/runner/AbstractEventBus.java rename to core/src/main/java/io/cucumber/core/eventbus/AbstractEventBus.java index 391b2285e7..c231ec72a4 100644 --- a/core/src/main/java/cucumber/runner/AbstractEventBus.java +++ b/core/src/main/java/io/cucumber/core/eventbus/AbstractEventBus.java @@ -1,8 +1,8 @@ -package cucumber.runner; +package io.cucumber.core.eventbus; -import cucumber.api.event.Event; +import io.cucumber.core.event.Event; -abstract class AbstractEventBus extends AbstractEventPublisher implements EventBus { +public abstract class AbstractEventBus extends AbstractEventPublisher implements EventBus { @Override public void send(Event event) { diff --git a/core/src/main/java/cucumber/runner/AbstractEventPublisher.java b/core/src/main/java/io/cucumber/core/eventbus/AbstractEventPublisher.java similarity index 86% rename from core/src/main/java/cucumber/runner/AbstractEventPublisher.java rename to core/src/main/java/io/cucumber/core/eventbus/AbstractEventPublisher.java index f3661b6ebb..b37a703cc6 100644 --- a/core/src/main/java/cucumber/runner/AbstractEventPublisher.java +++ b/core/src/main/java/io/cucumber/core/eventbus/AbstractEventPublisher.java @@ -1,15 +1,15 @@ -package cucumber.runner; +package io.cucumber.core.eventbus; -import cucumber.api.event.Event; -import cucumber.api.event.EventHandler; -import cucumber.api.event.EventPublisher; +import io.cucumber.core.event.Event; +import io.cucumber.core.event.EventHandler; +import io.cucumber.core.event.EventPublisher; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -class AbstractEventPublisher implements EventPublisher { +public abstract class AbstractEventPublisher implements EventPublisher { protected Map, List> handlers = new HashMap, List>(); @Override diff --git a/core/src/main/java/io/cucumber/core/eventbus/EventBus.java b/core/src/main/java/io/cucumber/core/eventbus/EventBus.java new file mode 100644 index 0000000000..c16bf48169 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/eventbus/EventBus.java @@ -0,0 +1,16 @@ +package io.cucumber.core.eventbus; + +import java.time.Instant; + +import io.cucumber.core.event.Event; +import io.cucumber.core.event.EventPublisher; + +public interface EventBus extends EventPublisher { + + Instant getInstant(); + + void send(Event event); + + void sendAll(Iterable queue); + +} diff --git a/core/src/main/java/cucumber/runtime/CompositeCucumberException.java b/core/src/main/java/io/cucumber/core/exception/CompositeCucumberException.java similarity index 78% rename from core/src/main/java/cucumber/runtime/CompositeCucumberException.java rename to core/src/main/java/io/cucumber/core/exception/CompositeCucumberException.java index c046234df6..4a168b569a 100644 --- a/core/src/main/java/cucumber/runtime/CompositeCucumberException.java +++ b/core/src/main/java/io/cucumber/core/exception/CompositeCucumberException.java @@ -1,12 +1,12 @@ -package cucumber.runtime; +package io.cucumber.core.exception; import java.util.Collections; import java.util.List; -class CompositeCucumberException extends CucumberException { +public class CompositeCucumberException extends CucumberException { private final List causes; - CompositeCucumberException(List causes) { + public CompositeCucumberException(List causes) { super(String.format("There were %d exceptions:", causes.size())); this.causes = causes; } diff --git a/core/src/main/java/cucumber/runtime/CucumberException.java b/core/src/main/java/io/cucumber/core/exception/CucumberException.java similarity index 89% rename from core/src/main/java/cucumber/runtime/CucumberException.java rename to core/src/main/java/io/cucumber/core/exception/CucumberException.java index 4ce81aa33a..07e58a1957 100644 --- a/core/src/main/java/cucumber/runtime/CucumberException.java +++ b/core/src/main/java/io/cucumber/core/exception/CucumberException.java @@ -1,4 +1,4 @@ -package cucumber.runtime; +package io.cucumber.core.exception; public class CucumberException extends RuntimeException { public CucumberException(String message) { diff --git a/core/src/main/java/cucumber/runtime/model/CucumberFeature.java b/core/src/main/java/io/cucumber/core/feature/CucumberFeature.java similarity index 83% rename from core/src/main/java/cucumber/runtime/model/CucumberFeature.java rename to core/src/main/java/io/cucumber/core/feature/CucumberFeature.java index c3990b0bf8..a51e79f412 100644 --- a/core/src/main/java/cucumber/runtime/model/CucumberFeature.java +++ b/core/src/main/java/io/cucumber/core/feature/CucumberFeature.java @@ -1,7 +1,5 @@ -package cucumber.runtime.model; +package io.cucumber.core.feature; -import cucumber.api.event.TestSourceRead; -import cucumber.runner.EventBus; import gherkin.ast.GherkinDocument; import gherkin.events.PickleEvent; @@ -40,11 +38,7 @@ public URI getUri() { return uri; } - public void sendTestSourceRead(EventBus bus) { - bus.send(new TestSourceRead(bus.getTime(), bus.getTimeMillis(), getUri().toString(), gherkinSource)); - } - - String getSource() { + public String getSource() { return gherkinSource; } diff --git a/core/src/main/java/cucumber/util/Encoding.java b/core/src/main/java/io/cucumber/core/feature/Encoding.java similarity index 52% rename from core/src/main/java/cucumber/util/Encoding.java rename to core/src/main/java/io/cucumber/core/feature/Encoding.java index 8fe0b94140..463fbfda99 100644 --- a/core/src/main/java/cucumber/util/Encoding.java +++ b/core/src/main/java/io/cucumber/core/feature/Encoding.java @@ -1,36 +1,45 @@ -package cucumber.util; +package io.cucumber.core.feature; -import cucumber.runtime.io.Resource; +import io.cucumber.core.io.Resource; +import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Locale.ROOT; /** * Utilities for reading the encoding of a file. */ -public class Encoding { +final class Encoding { private static final Pattern COMMENT_OR_EMPTY_LINE_PATTERN = Pattern.compile("^\\s*#|^\\s*$"); private static final Pattern ENCODING_PATTERN = Pattern.compile("^\\s*#\\s*encoding\\s*:\\s*([0-9a-zA-Z\\-]+)", Pattern.CASE_INSENSITIVE); - public static final String DEFAULT_ENCODING = "UTF-8"; + private static final String DEFAULT_ENCODING = UTF_8.name(); private static final String UTF_8_BOM = "\uFEFF"; - public static String readFile(Resource resource) throws RuntimeException, IOException { - String source = FixJava.readReader(new InputStreamReader(resource.getInputStream(), DEFAULT_ENCODING)); - // Remove UTF8 BOM encoded in first bytes - if (source.startsWith(UTF_8_BOM)) { - source = source.replaceFirst(UTF_8_BOM, ""); - } + static String readFile(Resource resource) throws RuntimeException, IOException { + String source = read(resource, DEFAULT_ENCODING); + // Remove UTF8 BOM encoded in first bytes + if (source.startsWith(UTF_8_BOM)) { + source = source.replaceFirst(UTF_8_BOM, ""); + } String enc = encoding(source); - if(!enc.equals(DEFAULT_ENCODING)) { - source = FixJava.readReader(new InputStreamReader(resource.getInputStream(), enc)); + if (!enc.equals(DEFAULT_ENCODING)) { + source = read(resource, enc); } return source; } + private static String read(Resource resource, String encoding) throws IOException { + try(BufferedReader br = new BufferedReader(new InputStreamReader(resource.getInputStream(), encoding))){ + return br.lines().collect(Collectors.joining(System.lineSeparator())); + } + } + private static String encoding(String source) { String encoding = DEFAULT_ENCODING; for (String line : source.split("\\n")) { diff --git a/core/src/main/java/cucumber/runtime/model/FeatureBuilder.java b/core/src/main/java/io/cucumber/core/feature/FeatureBuilder.java similarity index 91% rename from core/src/main/java/cucumber/runtime/model/FeatureBuilder.java rename to core/src/main/java/io/cucumber/core/feature/FeatureBuilder.java index caf45d871e..b128ea6f5f 100644 --- a/core/src/main/java/cucumber/runtime/model/FeatureBuilder.java +++ b/core/src/main/java/io/cucumber/core/feature/FeatureBuilder.java @@ -1,6 +1,6 @@ -package cucumber.runtime.model; +package io.cucumber.core.feature; -import cucumber.runtime.io.Resource; +import io.cucumber.core.io.Resource; import io.cucumber.core.logging.Logger; import io.cucumber.core.logging.LoggerFactory; @@ -10,7 +10,7 @@ import java.util.List; import java.util.Map; -public class FeatureBuilder { +final class FeatureBuilder { private final Logger log = LoggerFactory.getLogger(FeatureBuilder.class); private final Map sourceToFeature = new HashMap<>(); diff --git a/core/src/main/java/io/cucumber/core/model/FeatureIdentifier.java b/core/src/main/java/io/cucumber/core/feature/FeatureIdentifier.java similarity index 96% rename from core/src/main/java/io/cucumber/core/model/FeatureIdentifier.java rename to core/src/main/java/io/cucumber/core/feature/FeatureIdentifier.java index acf432e4b5..9d782ba67c 100644 --- a/core/src/main/java/io/cucumber/core/model/FeatureIdentifier.java +++ b/core/src/main/java/io/cucumber/core/feature/FeatureIdentifier.java @@ -1,5 +1,4 @@ -package io.cucumber.core.model; - +package io.cucumber.core.feature; import java.net.URI; /** diff --git a/core/src/main/java/cucumber/runtime/model/FeatureLoader.java b/core/src/main/java/io/cucumber/core/feature/FeatureLoader.java similarity index 87% rename from core/src/main/java/cucumber/runtime/model/FeatureLoader.java rename to core/src/main/java/io/cucumber/core/feature/FeatureLoader.java index 24fead395f..1f0636ab40 100644 --- a/core/src/main/java/cucumber/runtime/model/FeatureLoader.java +++ b/core/src/main/java/io/cucumber/core/feature/FeatureLoader.java @@ -1,8 +1,7 @@ -package cucumber.runtime.model; +package io.cucumber.core.feature; -import cucumber.runtime.io.Resource; -import cucumber.runtime.io.ResourceLoader; -import io.cucumber.core.model.FeatureIdentifier; +import io.cucumber.core.io.Resource; +import io.cucumber.core.io.ResourceLoader; import java.net.URI; import java.util.Iterator; diff --git a/core/src/main/java/cucumber/runtime/model/FeatureParser.java b/core/src/main/java/io/cucumber/core/feature/FeatureParser.java similarity index 93% rename from core/src/main/java/cucumber/runtime/model/FeatureParser.java rename to core/src/main/java/io/cucumber/core/feature/FeatureParser.java index 0de23a4893..8173f4ec43 100644 --- a/core/src/main/java/cucumber/runtime/model/FeatureParser.java +++ b/core/src/main/java/io/cucumber/core/feature/FeatureParser.java @@ -1,8 +1,5 @@ -package cucumber.runtime.model; +package io.cucumber.core.feature; -import cucumber.runtime.CucumberException; -import cucumber.runtime.io.Resource; -import cucumber.util.Encoding; import gherkin.AstBuilder; import gherkin.Parser; import gherkin.ParserException; @@ -11,6 +8,8 @@ import gherkin.events.PickleEvent; import gherkin.pickles.Compiler; import gherkin.pickles.Pickle; +import io.cucumber.core.exception.CucumberException; +import io.cucumber.core.io.Resource; import java.io.IOException; import java.net.URI; diff --git a/core/src/main/java/io/cucumber/core/model/FeaturePath.java b/core/src/main/java/io/cucumber/core/feature/FeaturePath.java similarity index 95% rename from core/src/main/java/io/cucumber/core/model/FeaturePath.java rename to core/src/main/java/io/cucumber/core/feature/FeaturePath.java index 719a2605c1..24824d6e19 100644 --- a/core/src/main/java/io/cucumber/core/model/FeaturePath.java +++ b/core/src/main/java/io/cucumber/core/feature/FeaturePath.java @@ -1,15 +1,14 @@ -package io.cucumber.core.model; +package io.cucumber.core.feature; import java.io.File; import java.net.URI; import java.net.URISyntaxException; import java.util.Locale; -import static io.cucumber.core.model.Classpath.CLASSPATH_SCHEME; -import static io.cucumber.core.model.Classpath.CLASSPATH_SCHEME_PREFIX; +import static io.cucumber.core.io.Classpath.CLASSPATH_SCHEME; +import static io.cucumber.core.io.Classpath.CLASSPATH_SCHEME_PREFIX; import static java.util.Objects.requireNonNull; - /** * A feature path is a URI to a single feature file or directory of features. *

diff --git a/core/src/main/java/io/cucumber/core/model/FeatureWithLines.java b/core/src/main/java/io/cucumber/core/feature/FeatureWithLines.java similarity index 97% rename from core/src/main/java/io/cucumber/core/model/FeatureWithLines.java rename to core/src/main/java/io/cucumber/core/feature/FeatureWithLines.java index ac2139ab04..021d3037ab 100644 --- a/core/src/main/java/io/cucumber/core/model/FeatureWithLines.java +++ b/core/src/main/java/io/cucumber/core/feature/FeatureWithLines.java @@ -1,4 +1,4 @@ -package io.cucumber.core.model; +package io.cucumber.core.feature; import java.io.Serializable; import java.net.URI; @@ -23,7 +23,7 @@ public class FeatureWithLines implements Serializable { private static final long serialVersionUID = 20190126L; private static final Pattern FEATURE_COLON_LINE_PATTERN = Pattern.compile("^(.*?):([\\d:]+)$"); - private static final String INVALID_PATH_MESSAGE = " is not valid. Try URI[:LINE]*"; + private static final String INVALID_PATH_MESSAGE = " is not valid. Try /.feature[:LINE]*"; private final URI uri; private final SortedSet lines; diff --git a/core/src/main/java/io/cucumber/core/model/GluePath.java b/core/src/main/java/io/cucumber/core/feature/GluePath.java similarity index 95% rename from core/src/main/java/io/cucumber/core/model/GluePath.java rename to core/src/main/java/io/cucumber/core/feature/GluePath.java index 2e13c30f13..1314fe8c66 100644 --- a/core/src/main/java/io/cucumber/core/model/GluePath.java +++ b/core/src/main/java/io/cucumber/core/feature/GluePath.java @@ -1,11 +1,9 @@ -package io.cucumber.core.model; +package io.cucumber.core.feature; import java.io.File; import java.net.URI; import java.net.URISyntaxException; -import static io.cucumber.core.model.Classpath.CLASSPATH_SCHEME; -import static io.cucumber.core.model.Classpath.CLASSPATH_SCHEME_PREFIX; import static java.lang.Character.isJavaIdentifierPart; import static java.lang.Character.isJavaIdentifierStart; import static java.util.Objects.requireNonNull; @@ -24,6 +22,9 @@ */ public class GluePath { + private static final String CLASSPATH_SCHEME = "classpath"; + private static final String CLASSPATH_SCHEME_PREFIX = CLASSPATH_SCHEME + ":"; + private GluePath(){ } diff --git a/core/src/main/java/io/cucumber/core/options/FeatureOptions.java b/core/src/main/java/io/cucumber/core/feature/Options.java similarity index 54% rename from core/src/main/java/io/cucumber/core/options/FeatureOptions.java rename to core/src/main/java/io/cucumber/core/feature/Options.java index 47450f625c..eeb5fc29c9 100644 --- a/core/src/main/java/io/cucumber/core/options/FeatureOptions.java +++ b/core/src/main/java/io/cucumber/core/feature/Options.java @@ -1,8 +1,8 @@ -package io.cucumber.core.options; +package io.cucumber.core.feature; import java.net.URI; import java.util.List; -public interface FeatureOptions { +public interface Options { List getFeaturePaths(); } diff --git a/core/src/main/java/io/cucumber/core/model/RerunLoader.java b/core/src/main/java/io/cucumber/core/feature/RerunLoader.java similarity index 77% rename from core/src/main/java/io/cucumber/core/model/RerunLoader.java rename to core/src/main/java/io/cucumber/core/feature/RerunLoader.java index 908a5dc031..b0c00670ab 100644 --- a/core/src/main/java/io/cucumber/core/model/RerunLoader.java +++ b/core/src/main/java/io/cucumber/core/feature/RerunLoader.java @@ -1,10 +1,10 @@ -package io.cucumber.core.model; +package io.cucumber.core.feature; -import cucumber.runtime.CucumberException; -import cucumber.runtime.io.Resource; -import cucumber.runtime.io.ResourceLoader; -import cucumber.util.FixJava; +import io.cucumber.core.exception.CucumberException; +import io.cucumber.core.io.Resource; +import io.cucumber.core.io.ResourceLoader; +import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.URI; @@ -12,6 +12,7 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; public class RerunLoader { private static final Pattern RERUN_PATH_SPECIFICATION = Pattern.compile("(?m:^| |)(.*?\\.feature(?:(?::\\d+)*))"); @@ -43,8 +44,8 @@ public List load(URI rerunPath) { } private static String read(Resource resource) { - try { - return FixJava.readReader(new InputStreamReader(resource.getInputStream())); + try (BufferedReader br = new BufferedReader(new InputStreamReader(resource.getInputStream()))) { + return br.lines().collect(Collectors.joining(System.lineSeparator())); } catch (IOException e) { throw new CucumberException("Failed to read resource:" + resource.getPath(), e); } diff --git a/core/src/main/java/cucumber/runtime/filter/Filters.java b/core/src/main/java/io/cucumber/core/filter/Filters.java similarity index 64% rename from core/src/main/java/cucumber/runtime/filter/Filters.java rename to core/src/main/java/io/cucumber/core/filter/Filters.java index d78d1f8bfc..339801c194 100644 --- a/core/src/main/java/cucumber/runtime/filter/Filters.java +++ b/core/src/main/java/io/cucumber/core/filter/Filters.java @@ -1,7 +1,6 @@ -package cucumber.runtime.filter; +package io.cucumber.core.filter; import gherkin.events.PickleEvent; -import io.cucumber.core.options.FilterOptions; import java.net.URI; import java.util.ArrayList; @@ -10,29 +9,28 @@ import java.util.Map; import java.util.regex.Pattern; -public class Filters { +public final class Filters { private final List filters; - - private int count; - public Filters(FilterOptions filterOptions) { + private int count; + public Filters(Options options) { filters = new ArrayList<>(); - List tagFilters = filterOptions.getTagFilters(); - if (!tagFilters.isEmpty()) { - this.filters.add(new TagPredicate(tagFilters)); + List tagExpressions = options.getTagExpressions(); + if (!tagExpressions.isEmpty()) { + this.filters.add(new TagPredicate(tagExpressions)); } - List nameFilters = filterOptions.getNameFilters(); + List nameFilters = options.getNameFilters(); if (!nameFilters.isEmpty()) { this.filters.add(new NamePredicate(nameFilters)); } - Map> lineFilters = filterOptions.getLineFilters(); + Map> lineFilters = options.getLineFilters(); if (!lineFilters.isEmpty()) { this.filters.add(new LinePredicate(lineFilters)); } - - this.count = filterOptions.getLimitCount(); + + this.count = options.getLimitCount(); } public boolean matchesFilters(PickleEvent pickleEvent) { @@ -43,7 +41,7 @@ public boolean matchesFilters(PickleEvent pickleEvent) { } return true; } - + public List limitPickleEvents(List pickleEvents) { if (count > pickleEvents.size() || count < 1) { return pickleEvents; diff --git a/core/src/main/java/cucumber/runtime/filter/LinePredicate.java b/core/src/main/java/io/cucumber/core/filter/LinePredicate.java similarity index 96% rename from core/src/main/java/cucumber/runtime/filter/LinePredicate.java rename to core/src/main/java/io/cucumber/core/filter/LinePredicate.java index 36560a607b..df43f1c5c3 100644 --- a/core/src/main/java/cucumber/runtime/filter/LinePredicate.java +++ b/core/src/main/java/io/cucumber/core/filter/LinePredicate.java @@ -1,4 +1,4 @@ -package cucumber.runtime.filter; +package io.cucumber.core.filter; import gherkin.events.PickleEvent; import gherkin.pickles.PickleLocation; diff --git a/core/src/main/java/cucumber/runtime/filter/NamePredicate.java b/core/src/main/java/io/cucumber/core/filter/NamePredicate.java similarity index 85% rename from core/src/main/java/cucumber/runtime/filter/NamePredicate.java rename to core/src/main/java/io/cucumber/core/filter/NamePredicate.java index b69ca5577d..37c1aff1ac 100644 --- a/core/src/main/java/cucumber/runtime/filter/NamePredicate.java +++ b/core/src/main/java/io/cucumber/core/filter/NamePredicate.java @@ -1,11 +1,11 @@ -package cucumber.runtime.filter; +package io.cucumber.core.filter; import gherkin.events.PickleEvent; import java.util.List; import java.util.regex.Pattern; -class NamePredicate implements PicklePredicate { +final class NamePredicate implements PicklePredicate { private List patterns; NamePredicate(List patterns) { diff --git a/core/src/main/java/io/cucumber/core/options/FilterOptions.java b/core/src/main/java/io/cucumber/core/filter/Options.java similarity index 68% rename from core/src/main/java/io/cucumber/core/options/FilterOptions.java rename to core/src/main/java/io/cucumber/core/filter/Options.java index 3a95be2ac4..e44a1ecd7a 100644 --- a/core/src/main/java/io/cucumber/core/options/FilterOptions.java +++ b/core/src/main/java/io/cucumber/core/filter/Options.java @@ -1,4 +1,4 @@ -package io.cucumber.core.options; +package io.cucumber.core.filter; import java.net.URI; import java.util.List; @@ -6,12 +6,12 @@ import java.util.Set; import java.util.regex.Pattern; -public interface FilterOptions { - List getNameFilters(); +public interface Options { + List getTagExpressions(); - List getTagFilters(); + List getNameFilters(); Map> getLineFilters(); - + int getLimitCount(); } diff --git a/core/src/main/java/cucumber/runtime/filter/PicklePredicate.java b/core/src/main/java/io/cucumber/core/filter/PicklePredicate.java similarity index 77% rename from core/src/main/java/cucumber/runtime/filter/PicklePredicate.java rename to core/src/main/java/io/cucumber/core/filter/PicklePredicate.java index e4c2a362df..0319fe9a16 100644 --- a/core/src/main/java/cucumber/runtime/filter/PicklePredicate.java +++ b/core/src/main/java/io/cucumber/core/filter/PicklePredicate.java @@ -1,4 +1,4 @@ -package cucumber.runtime.filter; +package io.cucumber.core.filter; import gherkin.events.PickleEvent; diff --git a/core/src/main/java/cucumber/runtime/filter/TagPredicate.java b/core/src/main/java/io/cucumber/core/filter/TagPredicate.java similarity index 52% rename from core/src/main/java/cucumber/runtime/filter/TagPredicate.java rename to core/src/main/java/io/cucumber/core/filter/TagPredicate.java index 9f1bcfe7e2..285eaad676 100644 --- a/core/src/main/java/cucumber/runtime/filter/TagPredicate.java +++ b/core/src/main/java/io/cucumber/core/filter/TagPredicate.java @@ -1,4 +1,4 @@ -package cucumber.runtime.filter; +package io.cucumber.core.filter; import gherkin.events.PickleEvent; import gherkin.pickles.PickleTag; @@ -9,24 +9,24 @@ import java.util.Collection; import java.util.List; -import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; -public class TagPredicate implements PicklePredicate { - private final List expressions = new ArrayList(); - private final List oldStyleExpressions = new ArrayList(); +public final class TagPredicate implements PicklePredicate { + private final List expressions = new ArrayList<>(); - public TagPredicate(List tagExpressions) { + public TagPredicate(String tagExpression) { + this(tagExpression.isEmpty() ? emptyList() : singletonList(tagExpression)); + } + + TagPredicate(List tagExpressions) { if (tagExpressions == null) { return; } TagExpressionParser parser = new TagExpressionParser(); for (String tagExpression : tagExpressions) { - if (TagExpressionOld.isOldTagExpression(tagExpression)) { - oldStyleExpressions.add(new TagExpressionOld(asList(tagExpression))); - } else { - expressions.add(parser.parse(tagExpression)); - } + expressions.add(parser.parse(tagExpression)); } } @@ -36,12 +36,11 @@ public boolean apply(PickleEvent pickleEvent) { } public boolean apply(Collection pickleTags) { - for (TagExpressionOld oldStyleExpression : oldStyleExpressions) { - if (!oldStyleExpression.evaluate(pickleTags)) { - return false; - } + if (expressions.isEmpty()) { + return true; } - List tags = new ArrayList(); + + List tags = new ArrayList<>(); for (PickleTag pickleTag : pickleTags) { tags.add(pickleTag.getName()); } diff --git a/core/src/main/java/cucumber/runtime/ClassFinder.java b/core/src/main/java/io/cucumber/core/io/ClassFinder.java similarity index 90% rename from core/src/main/java/cucumber/runtime/ClassFinder.java rename to core/src/main/java/io/cucumber/core/io/ClassFinder.java index a023adf286..f130020ba1 100644 --- a/core/src/main/java/cucumber/runtime/ClassFinder.java +++ b/core/src/main/java/io/cucumber/core/io/ClassFinder.java @@ -1,4 +1,4 @@ -package cucumber.runtime; +package io.cucumber.core.io; import java.net.URI; import java.util.Collection; diff --git a/core/src/main/java/io/cucumber/core/model/Classpath.java b/core/src/main/java/io/cucumber/core/io/Classpath.java similarity index 90% rename from core/src/main/java/io/cucumber/core/model/Classpath.java rename to core/src/main/java/io/cucumber/core/io/Classpath.java index 986bacef8b..1ab1999d4d 100644 --- a/core/src/main/java/io/cucumber/core/model/Classpath.java +++ b/core/src/main/java/io/cucumber/core/io/Classpath.java @@ -1,4 +1,4 @@ -package io.cucumber.core.model; +package io.cucumber.core.io; import java.net.URI; @@ -17,7 +17,7 @@ private Classpath() { * @param uri to resource * @return resource name */ - public static String resourceName(URI uri) { + static String resourceName(URI uri) { if (!CLASSPATH_SCHEME.equals(uri.getScheme())) { throw new IllegalArgumentException("uri must have classpath scheme " + uri); } diff --git a/core/src/main/java/cucumber/runtime/io/ClasspathResourceIterable.java b/core/src/main/java/io/cucumber/core/io/ClasspathResourceIterable.java similarity index 85% rename from core/src/main/java/cucumber/runtime/io/ClasspathResourceIterable.java rename to core/src/main/java/io/cucumber/core/io/ClasspathResourceIterable.java index 4cedbfdf2f..d13b3ae9a1 100644 --- a/core/src/main/java/cucumber/runtime/io/ClasspathResourceIterable.java +++ b/core/src/main/java/io/cucumber/core/io/ClasspathResourceIterable.java @@ -1,6 +1,6 @@ -package cucumber.runtime.io; +package io.cucumber.core.io; -import cucumber.runtime.CucumberException; +import io.cucumber.core.exception.CucumberException; import java.io.IOException; import java.net.URI; @@ -9,9 +9,10 @@ import java.util.Enumeration; import java.util.Iterator; -import static io.cucumber.core.model.Classpath.resourceName; +import static io.cucumber.core.io.Classpath.resourceName; + +final class ClasspathResourceIterable implements Iterable { -class ClasspathResourceIterable implements Iterable { private final ResourceIteratorFactory resourceIteratorFactory = new DelegatingResourceIteratorFactory(new ZipThenFileResourceIteratorFactory()); diff --git a/core/src/main/java/cucumber/runtime/io/ClasspathResourceLoader.java b/core/src/main/java/io/cucumber/core/io/ClasspathResourceLoader.java similarity index 63% rename from core/src/main/java/cucumber/runtime/io/ClasspathResourceLoader.java rename to core/src/main/java/io/cucumber/core/io/ClasspathResourceLoader.java index 6ce22d94f6..69b37ed44a 100644 --- a/core/src/main/java/cucumber/runtime/io/ClasspathResourceLoader.java +++ b/core/src/main/java/io/cucumber/core/io/ClasspathResourceLoader.java @@ -1,11 +1,11 @@ -package cucumber.runtime.io; +package io.cucumber.core.io; import java.net.URI; -public class ClasspathResourceLoader implements ResourceLoader { +final class ClasspathResourceLoader implements ResourceLoader { private final ClassLoader classLoader; - public ClasspathResourceLoader(ClassLoader classLoader) { + ClasspathResourceLoader(ClassLoader classLoader) { this.classLoader = classLoader; } diff --git a/core/src/main/java/cucumber/runtime/io/DelegatingResourceIteratorFactory.java b/core/src/main/java/io/cucumber/core/io/DelegatingResourceIteratorFactory.java similarity index 91% rename from core/src/main/java/cucumber/runtime/io/DelegatingResourceIteratorFactory.java rename to core/src/main/java/io/cucumber/core/io/DelegatingResourceIteratorFactory.java index 17704ac982..44b9fe5da6 100644 --- a/core/src/main/java/cucumber/runtime/io/DelegatingResourceIteratorFactory.java +++ b/core/src/main/java/io/cucumber/core/io/DelegatingResourceIteratorFactory.java @@ -1,6 +1,6 @@ -package cucumber.runtime.io; +package io.cucumber.core.io; -import cucumber.runtime.CucumberException; +import io.cucumber.core.exception.CucumberException; import java.net.URI; import java.util.Iterator; @@ -11,7 +11,7 @@ * A {@link ResourceIteratorFactory} implementation which delegates to * factories found by the ServiceLoader class. */ -class DelegatingResourceIteratorFactory implements ResourceIteratorFactory { +final class DelegatingResourceIteratorFactory implements ResourceIteratorFactory { private final Iterable delegates = ServiceLoader.load(ResourceIteratorFactory.class); diff --git a/core/src/main/java/cucumber/runtime/io/FileResource.java b/core/src/main/java/io/cucumber/core/io/FileResource.java similarity index 90% rename from core/src/main/java/cucumber/runtime/io/FileResource.java rename to core/src/main/java/io/cucumber/core/io/FileResource.java index ea334128da..2c6cb5c065 100644 --- a/core/src/main/java/cucumber/runtime/io/FileResource.java +++ b/core/src/main/java/io/cucumber/core/io/FileResource.java @@ -1,4 +1,4 @@ -package cucumber.runtime.io; +package io.cucumber.core.io; import java.io.File; import java.io.FileInputStream; @@ -7,10 +7,11 @@ import java.net.URI; import java.net.URISyntaxException; -import static io.cucumber.core.model.Classpath.CLASSPATH_SCHEME; -import static cucumber.runtime.io.MultiLoader.FILE_SCHEME; +import static io.cucumber.core.io.MultiLoader.FILE_SCHEME; +import static io.cucumber.core.io.Classpath.CLASSPATH_SCHEME; -class FileResource implements Resource { + +final class FileResource implements Resource { private final File root; private final File file; private final boolean classpathFileResource; diff --git a/core/src/main/java/cucumber/runtime/io/FileResourceIterable.java b/core/src/main/java/io/cucumber/core/io/FileResourceIterable.java similarity index 82% rename from core/src/main/java/cucumber/runtime/io/FileResourceIterable.java rename to core/src/main/java/io/cucumber/core/io/FileResourceIterable.java index a3deb5027a..e14a5c0140 100644 --- a/core/src/main/java/cucumber/runtime/io/FileResourceIterable.java +++ b/core/src/main/java/io/cucumber/core/io/FileResourceIterable.java @@ -1,9 +1,9 @@ -package cucumber.runtime.io; +package io.cucumber.core.io; import java.io.File; import java.util.Iterator; -class FileResourceIterable implements Iterable { +final class FileResourceIterable implements Iterable { private final File root; private final File file; private final String suffix; diff --git a/core/src/main/java/cucumber/runtime/io/FileResourceIterator.java b/core/src/main/java/io/cucumber/core/io/FileResourceIterator.java similarity index 95% rename from core/src/main/java/cucumber/runtime/io/FileResourceIterator.java rename to core/src/main/java/io/cucumber/core/io/FileResourceIterator.java index 320819343c..5a8462c210 100644 --- a/core/src/main/java/cucumber/runtime/io/FileResourceIterator.java +++ b/core/src/main/java/io/cucumber/core/io/FileResourceIterator.java @@ -1,13 +1,13 @@ -package cucumber.runtime.io; +package io.cucumber.core.io; import java.io.File; import java.io.FileFilter; import java.util.Iterator; -import static cucumber.runtime.io.Helpers.hasSuffix; +import static io.cucumber.core.io.Helpers.hasSuffix; import static java.util.Arrays.asList; -class FileResourceIterator implements Iterator { +final class FileResourceIterator implements Iterator { private final FlatteningIterator flatteningIterator = new FlatteningIterator(); static FileResourceIterator createFileResourceIterator(File root, File file, final String suffix) { diff --git a/core/src/main/java/cucumber/runtime/io/FileResourceIteratorFactory.java b/core/src/main/java/io/cucumber/core/io/FileResourceIteratorFactory.java similarity index 90% rename from core/src/main/java/cucumber/runtime/io/FileResourceIteratorFactory.java rename to core/src/main/java/io/cucumber/core/io/FileResourceIteratorFactory.java index 64cc457c07..666f2a777e 100644 --- a/core/src/main/java/cucumber/runtime/io/FileResourceIteratorFactory.java +++ b/core/src/main/java/io/cucumber/core/io/FileResourceIteratorFactory.java @@ -1,4 +1,4 @@ -package cucumber.runtime.io; +package io.cucumber.core.io; import java.io.File; import java.net.URI; @@ -14,7 +14,7 @@ * service implementation for {@link ResourceIteratorFactory} as it could * easily hide other service implementations.

*/ -class FileResourceIteratorFactory implements ResourceIteratorFactory { +final class FileResourceIteratorFactory implements ResourceIteratorFactory { @Override public boolean isFactoryFor(URI url) { diff --git a/core/src/main/java/cucumber/runtime/io/FileResourceLoader.java b/core/src/main/java/io/cucumber/core/io/FileResourceLoader.java similarity index 79% rename from core/src/main/java/cucumber/runtime/io/FileResourceLoader.java rename to core/src/main/java/io/cucumber/core/io/FileResourceLoader.java index d97b5cb8d5..fabc179ede 100644 --- a/core/src/main/java/cucumber/runtime/io/FileResourceLoader.java +++ b/core/src/main/java/io/cucumber/core/io/FileResourceLoader.java @@ -1,11 +1,11 @@ -package cucumber.runtime.io; +package io.cucumber.core.io; import java.io.File; import java.net.URI; -import static cucumber.runtime.io.MultiLoader.FILE_SCHEME; +import static io.cucumber.core.io.MultiLoader.FILE_SCHEME; -class FileResourceLoader implements ResourceLoader { +final class FileResourceLoader implements ResourceLoader { @Override public Iterable resources(URI path, String suffix) { if(!FILE_SCHEME.equals(path.getScheme())){ diff --git a/core/src/main/java/cucumber/runtime/io/FlatteningIterator.java b/core/src/main/java/io/cucumber/core/io/FlatteningIterator.java similarity index 94% rename from core/src/main/java/cucumber/runtime/io/FlatteningIterator.java rename to core/src/main/java/io/cucumber/core/io/FlatteningIterator.java index b8f3d98d72..5c17814472 100644 --- a/core/src/main/java/cucumber/runtime/io/FlatteningIterator.java +++ b/core/src/main/java/io/cucumber/core/io/FlatteningIterator.java @@ -1,11 +1,11 @@ -package cucumber.runtime.io; +package io.cucumber.core.io; import java.util.ArrayDeque; import java.util.Deque; import java.util.Iterator; import java.util.NoSuchElementException; -class FlatteningIterator implements Iterator { +final class FlatteningIterator implements Iterator { private final Deque> iterators = new ArrayDeque>(); private T next; diff --git a/core/src/main/java/cucumber/runtime/io/Helpers.java b/core/src/main/java/io/cucumber/core/io/Helpers.java similarity index 83% rename from core/src/main/java/cucumber/runtime/io/Helpers.java rename to core/src/main/java/io/cucumber/core/io/Helpers.java index 2e722c3239..a061c9289f 100644 --- a/core/src/main/java/cucumber/runtime/io/Helpers.java +++ b/core/src/main/java/io/cucumber/core/io/Helpers.java @@ -1,10 +1,10 @@ -package cucumber.runtime.io; +package io.cucumber.core.io; -import cucumber.runtime.CucumberException; +import io.cucumber.core.exception.CucumberException; import java.net.URI; -class Helpers { +final class Helpers { private Helpers() { } diff --git a/core/src/main/java/cucumber/runtime/io/MultiLoader.java b/core/src/main/java/io/cucumber/core/io/MultiLoader.java similarity index 83% rename from core/src/main/java/cucumber/runtime/io/MultiLoader.java rename to core/src/main/java/io/cucumber/core/io/MultiLoader.java index 984392fb10..cee8cebf9c 100644 --- a/core/src/main/java/cucumber/runtime/io/MultiLoader.java +++ b/core/src/main/java/io/cucumber/core/io/MultiLoader.java @@ -1,10 +1,10 @@ -package cucumber.runtime.io; +package io.cucumber.core.io; import java.net.URI; -import static io.cucumber.core.model.Classpath.CLASSPATH_SCHEME; +import static io.cucumber.core.io.Classpath.CLASSPATH_SCHEME; -public class MultiLoader implements ResourceLoader { +public final class MultiLoader implements ResourceLoader { static final String FILE_SCHEME = "file"; private final ClasspathResourceLoader classpath; diff --git a/core/src/main/java/cucumber/runtime/io/Resource.java b/core/src/main/java/io/cucumber/core/io/Resource.java similarity index 86% rename from core/src/main/java/cucumber/runtime/io/Resource.java rename to core/src/main/java/io/cucumber/core/io/Resource.java index e8b393f36a..129c865e5e 100644 --- a/core/src/main/java/cucumber/runtime/io/Resource.java +++ b/core/src/main/java/io/cucumber/core/io/Resource.java @@ -1,4 +1,4 @@ -package cucumber.runtime.io; +package io.cucumber.core.io; import java.io.IOException; import java.io.InputStream; diff --git a/core/src/main/java/cucumber/runtime/io/ResourceIteratorFactory.java b/core/src/main/java/io/cucumber/core/io/ResourceIteratorFactory.java similarity index 91% rename from core/src/main/java/cucumber/runtime/io/ResourceIteratorFactory.java rename to core/src/main/java/io/cucumber/core/io/ResourceIteratorFactory.java index 26533f00e2..72a38c6ae1 100644 --- a/core/src/main/java/cucumber/runtime/io/ResourceIteratorFactory.java +++ b/core/src/main/java/io/cucumber/core/io/ResourceIteratorFactory.java @@ -1,4 +1,4 @@ -package cucumber.runtime.io; +package io.cucumber.core.io; import java.net.URI; import java.util.Iterator; @@ -6,7 +6,7 @@ /** * Factory contract for creating resource iterators. */ -public interface ResourceIteratorFactory { +interface ResourceIteratorFactory { /** * Gets a value indicating whether the factory can create iterators for the diff --git a/core/src/main/java/cucumber/runtime/io/ResourceLoader.java b/core/src/main/java/io/cucumber/core/io/ResourceLoader.java similarity index 80% rename from core/src/main/java/cucumber/runtime/io/ResourceLoader.java rename to core/src/main/java/io/cucumber/core/io/ResourceLoader.java index 27d7d6081d..0a1ef56da1 100644 --- a/core/src/main/java/cucumber/runtime/io/ResourceLoader.java +++ b/core/src/main/java/io/cucumber/core/io/ResourceLoader.java @@ -1,4 +1,4 @@ -package cucumber.runtime.io; +package io.cucumber.core.io; import java.net.URI; diff --git a/core/src/main/java/cucumber/runtime/io/ResourceLoaderClassFinder.java b/core/src/main/java/io/cucumber/core/io/ResourceLoaderClassFinder.java similarity index 90% rename from core/src/main/java/cucumber/runtime/io/ResourceLoaderClassFinder.java rename to core/src/main/java/io/cucumber/core/io/ResourceLoaderClassFinder.java index 6bd2f57516..37d0eea084 100644 --- a/core/src/main/java/cucumber/runtime/io/ResourceLoaderClassFinder.java +++ b/core/src/main/java/io/cucumber/core/io/ResourceLoaderClassFinder.java @@ -1,13 +1,10 @@ -package cucumber.runtime.io; - -import cucumber.runtime.ClassFinder; -import io.cucumber.core.model.Classpath; +package io.cucumber.core.io; import java.net.URI; import java.util.Collection; import java.util.HashSet; -public class ResourceLoaderClassFinder implements ClassFinder { +public final class ResourceLoaderClassFinder implements ClassFinder { private static final String CLASS_SUFFIX = ".class"; private static final char DOT = '.'; private static final char PACKAGE_PATH_SEPARATOR = '/'; diff --git a/core/src/main/java/cucumber/runtime/io/ZipResource.java b/core/src/main/java/io/cucumber/core/io/ZipResource.java similarity index 84% rename from core/src/main/java/cucumber/runtime/io/ZipResource.java rename to core/src/main/java/io/cucumber/core/io/ZipResource.java index b9aacbf198..b6563bc8f1 100644 --- a/core/src/main/java/cucumber/runtime/io/ZipResource.java +++ b/core/src/main/java/io/cucumber/core/io/ZipResource.java @@ -1,4 +1,4 @@ -package cucumber.runtime.io; +package io.cucumber.core.io; import java.io.IOException; import java.io.InputStream; @@ -7,9 +7,9 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; -import static io.cucumber.core.model.Classpath.CLASSPATH_SCHEME; +import static io.cucumber.core.io.Classpath.CLASSPATH_SCHEME; -class ZipResource implements Resource { +final class ZipResource implements Resource { private final ZipFile jarFile; private final ZipEntry jarEntry; diff --git a/core/src/main/java/cucumber/runtime/io/ZipResourceIterator.java b/core/src/main/java/io/cucumber/core/io/ZipResourceIterator.java similarity index 94% rename from core/src/main/java/cucumber/runtime/io/ZipResourceIterator.java rename to core/src/main/java/io/cucumber/core/io/ZipResourceIterator.java index 74beca7789..a60e92e530 100644 --- a/core/src/main/java/cucumber/runtime/io/ZipResourceIterator.java +++ b/core/src/main/java/io/cucumber/core/io/ZipResourceIterator.java @@ -1,4 +1,4 @@ -package cucumber.runtime.io; +package io.cucumber.core.io; import java.io.File; import java.io.IOException; @@ -9,7 +9,7 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; -class ZipResourceIterator implements Iterator { +final class ZipResourceIterator implements Iterator { private final String path; private final String suffix; private final ZipFile jarFile; diff --git a/core/src/main/java/cucumber/runtime/io/ZipResourceIteratorFactory.java b/core/src/main/java/io/cucumber/core/io/ZipResourceIteratorFactory.java similarity index 80% rename from core/src/main/java/cucumber/runtime/io/ZipResourceIteratorFactory.java rename to core/src/main/java/io/cucumber/core/io/ZipResourceIteratorFactory.java index 886d01ed99..cdee083d1c 100644 --- a/core/src/main/java/cucumber/runtime/io/ZipResourceIteratorFactory.java +++ b/core/src/main/java/io/cucumber/core/io/ZipResourceIteratorFactory.java @@ -1,16 +1,16 @@ -package cucumber.runtime.io; +package io.cucumber.core.io; import java.io.IOException; import java.net.URI; import java.util.Iterator; -import cucumber.runtime.CucumberException; +import io.cucumber.core.exception.CucumberException; /** * Factory which creates {@link ZipResourceIterator}s for URL's with "jar", "zip" and "wsjar" * protocols. */ -class ZipResourceIteratorFactory implements ResourceIteratorFactory { +final class ZipResourceIteratorFactory implements ResourceIteratorFactory { @Override public boolean isFactoryFor(URI url) { diff --git a/core/src/main/java/cucumber/runtime/io/ZipThenFileResourceIteratorFactory.java b/core/src/main/java/io/cucumber/core/io/ZipThenFileResourceIteratorFactory.java similarity index 87% rename from core/src/main/java/cucumber/runtime/io/ZipThenFileResourceIteratorFactory.java rename to core/src/main/java/io/cucumber/core/io/ZipThenFileResourceIteratorFactory.java index 9d1404645a..5c323413bb 100644 --- a/core/src/main/java/cucumber/runtime/io/ZipThenFileResourceIteratorFactory.java +++ b/core/src/main/java/io/cucumber/core/io/ZipThenFileResourceIteratorFactory.java @@ -1,13 +1,12 @@ -package cucumber.runtime.io; +package io.cucumber.core.io; import java.net.URI; -import java.net.URL; import java.util.Iterator; /** * Resource iterator factory implementation which delegates to zip then file. */ -class ZipThenFileResourceIteratorFactory implements ResourceIteratorFactory { +final class ZipThenFileResourceIteratorFactory implements ResourceIteratorFactory { private final ResourceIteratorFactory zipResourceIteratorFactory = new ZipResourceIteratorFactory(); private final ResourceIteratorFactory fileResourceIteratorFactory = new FileResourceIteratorFactory(); diff --git a/core/src/main/java/io/cucumber/core/logging/Logger.java b/core/src/main/java/io/cucumber/core/logging/Logger.java index 6a2600ab2d..a5ca0a0119 100644 --- a/core/src/main/java/io/cucumber/core/logging/Logger.java +++ b/core/src/main/java/io/cucumber/core/logging/Logger.java @@ -17,62 +17,91 @@ public interface Logger { /** * Log the {@code message} at error level. + * + * @param message The message to log. */ void error(String message); /** * Log the {@code message} and {@code throwable} at error level. + * + * @param message The message to log. + * @param throwable The throwable to log. */ void error(String message, Throwable throwable); /** * Log the {@code message} at warning level. + * + * @param message The message to log. */ void warn(String message); /** * Log the {@code message} and {@code throwable} at warning level. + * + * @param message The message to log. + * @param throwable The throwable to log. */ void warn(String message, Throwable throwable); /** * Log the {@code message} at info level. + * + * @param message The message to log. */ void info(String message); /** * Log the {@code message} and {@code throwable} at info level. + * + * @param message The message to log. + * @param throwable The throwable to log. */ void info(String message, Throwable throwable); /** * Log the {@code message} at config level. + * + * @param message The message to log. */ void config(String message); /** * Log the {@code message} and {@code throwable} at config level. + * + * @param message The message to log. + * @param throwable The throwable to log. */ void config(String message, Throwable throwable); /** * Log the {@code message} at debug level. + * + * @param message The message to log. */ void debug(String message); /** * Log {@code message} and {@code throwable} at debug level. + * + * @param message The message to log. + * @param throwable The throwable to log. */ void debug(String message, Throwable throwable); /** * Log the {@code message} at trace level. + * + * @param message The message to log. */ void trace(String message); /** * Log the {@code message} and {@code throwable} at trace level. + * + * @param message The message to log. + * @param throwable The throwable to log. */ void trace(String message, Throwable throwable); - } diff --git a/core/src/main/java/io/cucumber/core/options/CommandlineOptionsParser.java b/core/src/main/java/io/cucumber/core/options/CommandlineOptionsParser.java index 4cb0e950e3..213bfbb39d 100644 --- a/core/src/main/java/io/cucumber/core/options/CommandlineOptionsParser.java +++ b/core/src/main/java/io/cucumber/core/options/CommandlineOptionsParser.java @@ -1,8 +1,8 @@ package io.cucumber.core.options; -import cucumber.runtime.io.MultiLoader; -import cucumber.runtime.io.ResourceLoader; -import io.cucumber.core.model.RerunLoader; +import io.cucumber.core.io.MultiLoader; +import io.cucumber.core.io.ResourceLoader; +import io.cucumber.core.feature.RerunLoader; import java.util.Arrays; import java.util.List; diff --git a/core/src/main/java/io/cucumber/core/options/Constants.java b/core/src/main/java/io/cucumber/core/options/Constants.java new file mode 100644 index 0000000000..7928e99aae --- /dev/null +++ b/core/src/main/java/io/cucumber/core/options/Constants.java @@ -0,0 +1,29 @@ +package io.cucumber.core.options; + +public final class Constants { + + /** + * File name of cucumber properties file: {@value} + */ + public static final String CUCUMBER_PROPERTIES_FILE_NAME = "cucumber.properties"; + + /** + * Property name used to pass command line options: {@value} + *

+ * When available it is recommended to use a property based alternative. + * + * @see RuntimeOptionsParser + */ + public static final String CUCUMBER_OPTIONS_PROPERTY_NAME = "cucumber.options"; + /** + * Property name used to select a specific object factory implementation: {@value} + * + * @see io.cucumber.core.backend.ObjectFactoryServiceLoader + */ + public static final String CUCUMBER_OBJECT_FACTORY_PROPERTY_NAME = "cucumber.object-factory"; + + + private Constants() { + + } +} diff --git a/core/src/main/java/io/cucumber/core/options/CucumberOptionsAnnotationParser.java b/core/src/main/java/io/cucumber/core/options/CucumberOptionsAnnotationParser.java index 8286b7b096..a3b61b4e04 100644 --- a/core/src/main/java/io/cucumber/core/options/CucumberOptionsAnnotationParser.java +++ b/core/src/main/java/io/cucumber/core/options/CucumberOptionsAnnotationParser.java @@ -1,24 +1,26 @@ package io.cucumber.core.options; -import cucumber.api.SnippetType; -import cucumber.runtime.CucumberException; -import cucumber.runtime.io.MultiLoader; -import cucumber.runtime.io.ResourceLoader; -import io.cucumber.core.model.Classpath; -import io.cucumber.core.model.FeaturePath; -import io.cucumber.core.model.FeatureWithLines; -import io.cucumber.core.model.GluePath; -import io.cucumber.core.model.RerunLoader; +import io.cucumber.core.snippets.SnippetType; +import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.exception.CucumberException; +import io.cucumber.core.io.MultiLoader; +import io.cucumber.core.io.ResourceLoader; +import io.cucumber.core.io.Classpath; +import io.cucumber.core.feature.FeaturePath; +import io.cucumber.core.feature.FeatureWithLines; +import io.cucumber.core.feature.GluePath; +import io.cucumber.core.feature.RerunLoader; import java.net.URI; import java.util.regex.Pattern; +import static java.util.Objects.requireNonNull; + public final class CucumberOptionsAnnotationParser { private final RerunLoader rerunLoader; private boolean featuresSpecified = false; private boolean overridingGlueSpecified = false; private OptionsProvider optionsProvider; - private CoreCucumberOptionsProvider coreCucumberOptionsProvider = new CoreCucumberOptionsProvider(); public CucumberOptionsAnnotationParser() { this(new MultiLoader(CucumberOptionsAnnotationParser.class.getClassLoader())); @@ -37,13 +39,8 @@ public RuntimeOptionsBuilder parse(Class clazz) { RuntimeOptionsBuilder args = new RuntimeOptionsBuilder(); for (Class classWithOptions = clazz; hasSuperClass(classWithOptions); classWithOptions = classWithOptions.getSuperclass()) { - CucumberOptions options = null; - if (optionsProvider != null) { - options = optionsProvider.getOptions(classWithOptions); - } - if(options == null){ - options = coreCucumberOptionsProvider.getOptions(classWithOptions); - } + CucumberOptions options = requireNonNull(optionsProvider).getOptions(classWithOptions); + if (options != null) { addDryRun(options, args); addMonochrome(options, args); @@ -54,7 +51,7 @@ public RuntimeOptionsBuilder parse(Class clazz) { addSnippets(options, args); addGlue(options, args); addFeatures(options, args); - addJunitOptions(options, args); + addObjectFactory(options, args); } } addDefaultFeaturePathIfNoFeaturePathIsSpecified(args, clazz); @@ -151,16 +148,15 @@ private void addDefaultGlueIfNoOverridingGlueIsSpecified(RuntimeOptionsBuilder a } } - private void addStrict(CucumberOptions options, RuntimeOptionsBuilder args) { if (options.strict()) { args.setStrict(true); } } - private void addJunitOptions(CucumberOptions options, RuntimeOptionsBuilder args) { - for (String junitOption : options.junit()) { - args.addJunitOption(junitOption); + private void addObjectFactory(CucumberOptions options, RuntimeOptionsBuilder args) { + if (options.objectFactory() != null) { + args.setObjectFactoryClass(options.objectFactory()); } } @@ -174,7 +170,7 @@ private static String packagePath(Class clazz) { return Classpath.CLASSPATH_SCHEME_PREFIX + packageName.replace('.', '/'); } - static String packageName(Class clazz) { + private static String packageName(Class clazz) { String className = clazz.getName(); return className.substring(0, Math.max(0, className.lastIndexOf('.'))); } @@ -215,81 +211,7 @@ public interface CucumberOptions { String[] name(); SnippetType snippets(); - - String[] junit(); - } - - private static class CoreCucumberOptions implements CucumberOptions { - private final cucumber.api.CucumberOptions annotation; - - CoreCucumberOptions(cucumber.api.CucumberOptions annotation) { - this.annotation = annotation; - } - - @Override - public boolean dryRun() { - return annotation.dryRun(); - } - - @Override - public boolean strict() { - return annotation.strict(); - } - - @Override - public String[] features() { - return annotation.features(); - } - - @Override - public String[] glue() { - return annotation.glue(); - } - - @Override - public String[] extraGlue() { - return annotation.extraGlue(); - } - - @Override - public String[] tags() { - return annotation.tags(); - } - - @Override - public String[] plugin() { - return annotation.plugin(); - } - - @Override - public boolean monochrome() { - return annotation.monochrome(); - } - - @Override - public String[] name() { - return annotation.name(); - } - - @Override - public SnippetType snippets() { - return annotation.snippets(); - } - - @Override - public String[] junit() { - return annotation.junit(); - } - } - - private static class CoreCucumberOptionsProvider implements OptionsProvider { - @Override - public CucumberOptions getOptions(Class clazz) { - final cucumber.api.CucumberOptions annotation = clazz.getAnnotation(cucumber.api.CucumberOptions.class); - if (annotation == null) { - return null; - } - return new CoreCucumberOptions(annotation); - } + + Class objectFactory(); } } diff --git a/core/src/main/java/io/cucumber/core/options/CucumberProperties.java b/core/src/main/java/io/cucumber/core/options/CucumberProperties.java new file mode 100644 index 0000000000..aec262e7f7 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/options/CucumberProperties.java @@ -0,0 +1,124 @@ +package io.cucumber.core.options; + +import io.cucumber.core.logging.Logger; +import io.cucumber.core.logging.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.util.*; + +import static io.cucumber.core.options.Constants.CUCUMBER_PROPERTIES_FILE_NAME; + +public final class CucumberProperties { + + private static final Logger log = LoggerFactory.getLogger(CucumberProperties.class); + + private CucumberProperties() { + + } + + public static Map create() { + CucumberPropertiesMap fromBundle = new CucumberPropertiesMap(fromPropertiesFile()); + CucumberPropertiesMap fromEnvironmentProperties = new CucumberPropertiesMap(fromBundle, fromEnvironment()); + return new CucumberPropertiesMap(fromEnvironmentProperties, fromSystemProperties()); + } + + public static Map fromPropertiesFile() { + InputStream resourceAsStream = CucumberProperties.class.getResourceAsStream("/" + CUCUMBER_PROPERTIES_FILE_NAME); + if (resourceAsStream == null) { + log.debug(CUCUMBER_PROPERTIES_FILE_NAME + " file did not exist"); + return Collections.emptyMap(); + } + + try { + Properties properties = new Properties(); + properties.load(resourceAsStream); + return CucumberPropertiesMap.create(properties); + } catch (IOException e) { + log.error(CUCUMBER_PROPERTIES_FILE_NAME + " could not be loaded", e); + throw new RuntimeException(e); + } + } + + public static Map fromSystemProperties() { + Properties p = System.getProperties(); + return CucumberPropertiesMap.create(p); + } + + public static Map fromEnvironment() { + Map p = System.getenv(); + CucumberPropertiesMap properties = new CucumberPropertiesMap(); + properties.putAll(p); + return properties; + } + + static class CucumberPropertiesMap extends HashMap { + + private final CucumberPropertiesMap parent; + + CucumberPropertiesMap() { + this(null); + } + + CucumberPropertiesMap(CucumberPropertiesMap parent) { + this(parent, Collections.emptyMap()); + } + + CucumberPropertiesMap(Map properties) { + this(null, properties); + } + + CucumberPropertiesMap(CucumberPropertiesMap parent, Map properties) { + super(properties); + this.parent = parent; + } + + private static CucumberPropertiesMap create(Properties p) { + CucumberPropertiesMap properties = new CucumberPropertiesMap(); + for (String key : p.stringPropertyNames()) { + properties.put(key, p.getProperty(key)); + } + return properties; + } + + @Override + public String get(Object key) { + String exactMatch = super.get(key); + if (exactMatch != null) { + return exactMatch; + } + + if (!(key instanceof String)) { + return null; + } + + // Support old skool + // Not all environments allow properties to contain dots or dashes. + // So we map the requested property to its underscore case variant. + String keyString = (String) key; + + String uppercase = keyString + .replace(".", "_") + .replace("-", "_") + .toUpperCase(Locale.ENGLISH); + String upperCaseMatch = super.get(uppercase); + if (upperCaseMatch != null) { + return upperCaseMatch; + } + + String lowercase = keyString + .replace(".", "_") + .replace("-", "_") + .toLowerCase(Locale.ENGLISH); + String lowerValue = super.get(lowercase); + if (lowerValue != null) + return lowerValue; + + if (parent == null) { + return null; + } + return parent.get(key); + } + + } +} diff --git a/core/src/main/java/io/cucumber/core/options/CucumberPropertiesParser.java b/core/src/main/java/io/cucumber/core/options/CucumberPropertiesParser.java new file mode 100644 index 0000000000..97a4d7da9b --- /dev/null +++ b/core/src/main/java/io/cucumber/core/options/CucumberPropertiesParser.java @@ -0,0 +1,62 @@ +package io.cucumber.core.options; + +import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.exception.CucumberException; +import io.cucumber.core.io.MultiLoader; +import io.cucumber.core.io.ResourceLoader; +import io.cucumber.core.feature.RerunLoader; + +import java.util.List; +import java.util.Map; + +import static io.cucumber.core.options.Constants.CUCUMBER_OBJECT_FACTORY_PROPERTY_NAME; +import static io.cucumber.core.options.Constants.CUCUMBER_OPTIONS_PROPERTY_NAME; + +public final class CucumberPropertiesParser { + + private final ResourceLoader resourceLoader; + + public CucumberPropertiesParser(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + } + + public CucumberPropertiesParser() { + this(new MultiLoader(CucumberPropertiesParser.class.getClassLoader())); + } + + public RuntimeOptionsBuilder parse(Map properties) { + final RuntimeOptionsBuilder builder; + String cucumberOptions = properties.get(CUCUMBER_OPTIONS_PROPERTY_NAME); + if (cucumberOptions != null) { + RerunLoader rerunLoader = new RerunLoader(resourceLoader); + RuntimeOptionsParser parser = new RuntimeOptionsParser(rerunLoader); + List args = ShellWords.parse(cucumberOptions); + builder = parser.parse(args); + } else { + builder = new RuntimeOptionsBuilder(); + } + + String cucumberObjectFactory = properties.get(CUCUMBER_OBJECT_FACTORY_PROPERTY_NAME); + if (cucumberObjectFactory != null) { + Class objectFactoryClass = parseObjectFactory(cucumberObjectFactory); + builder.setObjectFactoryClass(objectFactoryClass); + } + + return builder; + } + + @SuppressWarnings("unchecked") + static Class parseObjectFactory(String cucumberObjectFactory) { + Class objectFactoryClass; + try { + objectFactoryClass = Class.forName(cucumberObjectFactory); + } catch (ClassNotFoundException e) { + throw new CucumberException("Could not load object factory class for " + cucumberObjectFactory, e); + } + if (!ObjectFactory.class.isAssignableFrom(objectFactoryClass)) { + throw new CucumberException("Object factory class " + objectFactoryClass + " was not a subclass of " + ObjectFactory.class); + } + return (Class) objectFactoryClass; + } + +} diff --git a/core/src/main/java/io/cucumber/core/options/EnvironmentOptionsParser.java b/core/src/main/java/io/cucumber/core/options/EnvironmentOptionsParser.java deleted file mode 100644 index 7eb36ac487..0000000000 --- a/core/src/main/java/io/cucumber/core/options/EnvironmentOptionsParser.java +++ /dev/null @@ -1,37 +0,0 @@ -package io.cucumber.core.options; - -import cucumber.runtime.Env; -import cucumber.runtime.Shellwords; -import cucumber.runtime.io.MultiLoader; -import cucumber.runtime.io.ResourceLoader; -import io.cucumber.core.model.RerunLoader; - -import java.util.Collections; -import java.util.List; - -public class EnvironmentOptionsParser { - - private final ResourceLoader resourceLoader; - - - public EnvironmentOptionsParser(ResourceLoader resourceLoader) { - this.resourceLoader = resourceLoader; - } - - public EnvironmentOptionsParser() { - this(new MultiLoader(EnvironmentOptionsParser.class.getClassLoader())); - } - - public RuntimeOptionsBuilder parse(Env env){ - RerunLoader rerunLoader = new RerunLoader(resourceLoader); - RuntimeOptionsParser parser = new RuntimeOptionsParser(rerunLoader); - - String cucumberOptionsFromEnv = env.get("cucumber.options"); - List shellWords = Collections.emptyList(); - if (cucumberOptionsFromEnv != null) { - shellWords = Shellwords.parse(cucumberOptionsFromEnv); - } - return parser.parse(shellWords); - } - -} diff --git a/core/src/main/java/io/cucumber/core/options/PluginOption.java b/core/src/main/java/io/cucumber/core/options/PluginOption.java new file mode 100644 index 0000000000..6016f167f8 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/options/PluginOption.java @@ -0,0 +1,128 @@ +package io.cucumber.core.options; + +import io.cucumber.core.plugin.Plugin; +import io.cucumber.core.plugin.SummaryPrinter; +import io.cucumber.core.plugin.ConcurrentEventListener; +import io.cucumber.core.plugin.EventListener; +import io.cucumber.core.exception.CucumberException; +import io.cucumber.core.logging.Logger; +import io.cucumber.core.logging.LoggerFactory; +import io.cucumber.core.plugin.DefaultSummaryPrinter; +import io.cucumber.core.plugin.HTMLFormatter; +import io.cucumber.core.plugin.JSONFormatter; +import io.cucumber.core.plugin.JUnitFormatter; +import io.cucumber.core.plugin.NullSummaryPrinter; +import io.cucumber.core.plugin.Options; +import io.cucumber.core.plugin.PrettyFormatter; +import io.cucumber.core.plugin.ProgressFormatter; +import io.cucumber.core.plugin.RerunFormatter; +import io.cucumber.core.plugin.TestNGFormatter; +import io.cucumber.core.plugin.TimelineFormatter; +import io.cucumber.core.plugin.UnusedStepsSummaryPrinter; +import io.cucumber.core.plugin.UsageFormatter; + +import java.util.HashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class PluginOption implements Options.Plugin { + + private static final Logger log = LoggerFactory.getLogger(PluginOption.class); + + private static final Pattern PLUGIN_WITH_ARGUMENT_PATTERN = Pattern.compile("([^:]+):(.*)"); + private static final HashMap> PLUGIN_CLASSES = new HashMap>() {{ + put("junit", JUnitFormatter.class); + put("testng", TestNGFormatter.class); + put("html", HTMLFormatter.class); + put("pretty", PrettyFormatter.class); + put("progress", ProgressFormatter.class); + put("json", JSONFormatter.class); + put("usage", UsageFormatter.class); + put("rerun", RerunFormatter.class); + put("summary", DefaultSummaryPrinter.class); + put("default_summary", DefaultSummaryPrinter.class); + put("null_summary", NullSummaryPrinter.class); + put("unused", UnusedStepsSummaryPrinter.class); + put("timeline", TimelineFormatter.class); + }}; + + // Refuse plugins known to implement the old API + private static final HashMap> OLD_INTELLIJ_IDEA_PLUGIN_CLASSES = new HashMap>() {{ + put("org.jetbrains.plugins.cucumber.java.run.CucumberJvmSMFormatter", PrettyFormatter.class); + put("org.jetbrains.plugins.cucumber.java.run.CucumberJvm2SMFormatter", PrettyFormatter.class); + put("org.jetbrains.plugins.cucumber.java.run.CucumberJvm3SMFormatter", PrettyFormatter.class); + }}; + + private final String pluginString; + private final Class pluginClass; + private final String argument; + + private PluginOption(String pluginString, Class pluginClass, String argument) { + this.pluginString = pluginString; + this.pluginClass = pluginClass; + this.argument = argument; + } + + @Override + public Class pluginClass() { + return pluginClass; + } + + @Override + public String argument() { + return argument; + } + + @Override + public String pluginString() { + return pluginString; + } + + boolean isFormatter() { + return EventListener.class.isAssignableFrom(pluginClass) || ConcurrentEventListener.class.isAssignableFrom(pluginClass); + } + + boolean isSummaryPrinter() { + return SummaryPrinter.class.isAssignableFrom(pluginClass); + } + + static PluginOption parse(String pluginArgumentPattern) { + Matcher pluginWithFile = PLUGIN_WITH_ARGUMENT_PATTERN.matcher(pluginArgumentPattern); + if (!pluginWithFile.matches()) { + return new PluginOption(pluginArgumentPattern, parsePluginName(pluginArgumentPattern), null); + } + + Class pluginClass = parsePluginName(pluginWithFile.group(1)); + return new PluginOption(pluginArgumentPattern, pluginClass, pluginWithFile.group(2)); + } + + private static Class parsePluginName(String pluginName) { + Class oldApiPlugin = OLD_INTELLIJ_IDEA_PLUGIN_CLASSES.get(pluginName); + if (oldApiPlugin != null) { + log.warn("Incompatible IntelliJ IDEA Plugin detected. Falling back to pretty formatter"); + return oldApiPlugin; + } + + Class pluginClass = PLUGIN_CLASSES.get(pluginName); + if (pluginClass == null) { + pluginClass = loadClass(pluginName); + } + return pluginClass; + } + + @SuppressWarnings("unchecked") + private static Class loadClass(String className) { + try { + Class aClass = Thread.currentThread().getContextClassLoader().loadClass(className); + + if (Plugin.class.isAssignableFrom(aClass)) { + return (Class) aClass; + } + throw new CucumberException("Couldn't load plugin class: " + className + ". It does not implement " + Plugin.class.getName()); + } catch (ClassNotFoundException e) { + throw new CucumberException("Couldn't load plugin class: " + className, e); + } + } + + +} diff --git a/core/src/main/java/io/cucumber/core/options/PluginOptions.java b/core/src/main/java/io/cucumber/core/options/PluginOptions.java deleted file mode 100644 index 680158d5a0..0000000000 --- a/core/src/main/java/io/cucumber/core/options/PluginOptions.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.cucumber.core.options; - -import java.util.List; - -public interface PluginOptions { - List getPluginNames(); - - boolean isStrict(); - - boolean isMonochrome(); - -} diff --git a/core/src/main/java/io/cucumber/core/options/RunnerOptions.java b/core/src/main/java/io/cucumber/core/options/RunnerOptions.java deleted file mode 100644 index 6278874be3..0000000000 --- a/core/src/main/java/io/cucumber/core/options/RunnerOptions.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.cucumber.core.options; - -import cucumber.api.SnippetType; - -import java.net.URI; -import java.util.List; - -public interface RunnerOptions { - List getGlue(); - - boolean isDryRun(); - - SnippetType getSnippetType(); -} diff --git a/core/src/main/java/io/cucumber/core/options/RuntimeOptions.java b/core/src/main/java/io/cucumber/core/options/RuntimeOptions.java index 7862525eb2..04d131cb25 100644 --- a/core/src/main/java/io/cucumber/core/options/RuntimeOptions.java +++ b/core/src/main/java/io/cucumber/core/options/RuntimeOptions.java @@ -1,8 +1,9 @@ package io.cucumber.core.options; -import cucumber.api.SnippetType; -import cucumber.runtime.order.PickleOrder; -import cucumber.runtime.order.StandardPickleOrders; +import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.order.PickleOrder; +import io.cucumber.core.order.StandardPickleOrders; +import io.cucumber.core.snippets.SnippetType; import java.net.URI; import java.util.ArrayList; @@ -17,15 +18,19 @@ import static java.util.Collections.unmodifiableList; import static java.util.Collections.unmodifiableMap; -public final class RuntimeOptions implements FeatureOptions, FilterOptions, PluginOptions, RunnerOptions { +public final class RuntimeOptions implements + io.cucumber.core.feature.Options, + io.cucumber.core.runner.Options, + io.cucumber.core.plugin.Options, + io.cucumber.core.filter.Options, + io.cucumber.core.backend.Options { private final List glue = new ArrayList<>(); - private final List tagFilters = new ArrayList<>(); + private final List tagExpressions = new ArrayList<>(); private final List nameFilters = new ArrayList<>(); private final Map> lineFilters = new HashMap<>(); private final SortedSet featurePaths = new TreeSet<>(); - private final List junitOptions = new ArrayList<>(); private boolean dryRun; private boolean strict = false; private boolean monochrome = false; @@ -35,9 +40,9 @@ public final class RuntimeOptions implements FeatureOptions, FilterOptions, Plug private PickleOrder pickleOrder = StandardPickleOrders.lexicalUriOrder(); private int count = 0; - private final List pluginFormatterNames = new ArrayList<>(); - private final List pluginStepDefinitionReporterNames = new ArrayList<>(); - private final List pluginSummaryPrinterNames = new ArrayList<>(); + private final List formatters = new ArrayList<>(); + private final List summaryPrinters = new ArrayList<>(); + private Class objectFactoryClass; private RuntimeOptions() { @@ -51,16 +56,12 @@ public int getCount() { return count; } - List getPluginFormatterNames() { - return pluginFormatterNames; + List getFormatters() { + return formatters; } - List getPluginStepDefinitionReporterNames() { - return pluginStepDefinitionReporterNames; - } - - List getPluginSummaryPrinterNames() { - return pluginSummaryPrinterNames; + List getSummaryPrinter() { + return summaryPrinters; } public boolean isMultiThreaded() { @@ -68,12 +69,11 @@ public boolean isMultiThreaded() { } @Override - public List getPluginNames() { - List pluginNames = new ArrayList<>(); - pluginNames.addAll(getPluginFormatterNames()); - pluginNames.addAll(getPluginStepDefinitionReporterNames()); - pluginNames.addAll(getPluginSummaryPrinterNames()); - return pluginNames; + public List plugins() { + List plugins = new ArrayList<>(); + plugins.addAll(formatters); + plugins.addAll(summaryPrinters); + return plugins; } @Override @@ -101,13 +101,13 @@ public List getFeaturePaths() { } @Override - public List getNameFilters() { - return unmodifiableList(nameFilters); + public List getTagExpressions() { + return unmodifiableList(tagExpressions); } @Override - public List getTagFilters() { - return unmodifiableList(tagFilters); + public List getNameFilters() { + return unmodifiableList(nameFilters); } void setCount(int count) { @@ -124,11 +124,6 @@ void setGlue(List parsedGlue) { glue.addAll(parsedGlue); } - void setJunitOptions(List junitOptions) { - this.junitOptions.clear(); - this.junitOptions.addAll(junitOptions); - } - void setLineFilters(Map> lineFilters) { this.lineFilters.clear(); for (URI path : lineFilters.keySet()) { @@ -145,9 +140,9 @@ void setPickleOrder(PickleOrder pickleOrder) { this.pickleOrder = pickleOrder; } - void setTagFilters(List tagFilters) { - this.tagFilters.clear(); - this.tagFilters.addAll(tagFilters); + void setTagExpressions(List tagExpressions) { + this.tagExpressions.clear(); + this.tagExpressions.addAll(tagExpressions); } @Override @@ -170,8 +165,9 @@ public SnippetType getSnippetType() { return snippetType; } - public List getJunitOptions() { - return unmodifiableList(junitOptions); + @Override + public Class getObjectFactoryClass() { + return objectFactoryClass; } public int getThreads() { @@ -205,4 +201,8 @@ void setThreads(int threads) { void setWip(boolean wip) { this.wip = wip; } + + void setObjectFactoryClass(Class objectFactoryClass) { + this.objectFactoryClass = objectFactoryClass; + } } diff --git a/core/src/main/java/io/cucumber/core/options/RuntimeOptionsBuilder.java b/core/src/main/java/io/cucumber/core/options/RuntimeOptionsBuilder.java index a9b7172a7e..d5f152b4ca 100644 --- a/core/src/main/java/io/cucumber/core/options/RuntimeOptionsBuilder.java +++ b/core/src/main/java/io/cucumber/core/options/RuntimeOptionsBuilder.java @@ -1,10 +1,11 @@ package io.cucumber.core.options; -import cucumber.api.SnippetType; -import cucumber.runtime.CucumberException; -import cucumber.runtime.formatter.PluginFactory; -import cucumber.runtime.order.PickleOrder; -import io.cucumber.core.model.FeatureWithLines; +import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.snippets.SnippetType; +import io.cucumber.core.exception.CucumberException; +import io.cucumber.core.feature.FeatureWithLines; +import io.cucumber.core.order.PickleOrder; +import io.cucumber.core.plugin.Options; import java.net.URI; import java.util.ArrayList; @@ -23,7 +24,6 @@ public final class RuntimeOptionsBuilder { private List parsedFeaturePaths = new ArrayList<>(); private List parsedGlue = new ArrayList<>(); private ParsedPluginData parsedPluginData = new ParsedPluginData(); - private List parsedJunitOptions = new ArrayList<>(); private boolean parsedIsRerun = false; private Integer parsedThreads = null; private Boolean parsedDryRun = null; @@ -33,6 +33,7 @@ public final class RuntimeOptionsBuilder { private Boolean parsedWip = null; private PickleOrder parsedPickleOrder = null; private Integer parsedCount = null; + private Class parsedObjectFactoryClass = null; public RuntimeOptionsBuilder addFeature(FeatureWithLines featureWithLines) { parsedFeaturePaths.add(featureWithLines.uri()); @@ -45,11 +46,6 @@ public RuntimeOptionsBuilder addGlue(URI glue) { return this; } - public RuntimeOptionsBuilder addJunitOption(String junitOption) { - this.parsedJunitOptions.add(junitOption); - return this; - } - private RuntimeOptionsBuilder addLineFilters(FeatureWithLines featureWithLines) { URI key = featureWithLines.uri(); Set lines = featureWithLines.lines(); @@ -121,7 +117,7 @@ public RuntimeOptions build(RuntimeOptions runtimeOptions) { runtimeOptions.setLineFilters(Collections.>emptyMap()); } if (!this.parsedTagFilters.isEmpty() || !this.parsedNameFilters.isEmpty() || !this.parsedLineFilters.isEmpty()) { - runtimeOptions.setTagFilters(this.parsedTagFilters); + runtimeOptions.setTagExpressions(this.parsedTagFilters); runtimeOptions.setNameFilters(this.parsedNameFilters); runtimeOptions.setLineFilters(this.parsedLineFilters); } @@ -132,13 +128,13 @@ public RuntimeOptions build(RuntimeOptions runtimeOptions) { if (!this.parsedGlue.isEmpty()) { runtimeOptions.setGlue(this.parsedGlue); } - if (!this.parsedJunitOptions.isEmpty()) { - runtimeOptions.setJunitOptions(this.parsedJunitOptions); - } - this.parsedPluginData.updatePluginFormatterNames(runtimeOptions.getPluginFormatterNames()); - this.parsedPluginData.updatePluginStepDefinitionReporterNames(runtimeOptions.getPluginStepDefinitionReporterNames()); - this.parsedPluginData.updatePluginSummaryPrinterNames(runtimeOptions.getPluginSummaryPrinterNames()); + this.parsedPluginData.updateFormatters(runtimeOptions.getFormatters()); + this.parsedPluginData.updateSummaryPrinters(runtimeOptions.getSummaryPrinter()); + + if (parsedObjectFactoryClass != null) { + runtimeOptions.setObjectFactoryClass(parsedObjectFactoryClass); + } return runtimeOptions; } @@ -209,66 +205,63 @@ public RuntimeOptionsBuilder addDefaultFormatterIfNotPresent() { return this; } - private static class ParsedPluginData { - ParsedOptionNames formatterNames = new ParsedOptionNames(); - ParsedOptionNames stepDefinitionReporterNames = new ParsedOptionNames(); - ParsedOptionNames summaryPrinterNames = new ParsedOptionNames(); + public void setObjectFactoryClass(Class objectFactoryClass) { + this.parsedObjectFactoryClass = objectFactoryClass; + } + + static final class ParsedPluginData { + private ParsedPlugins formatters = new ParsedPlugins(); + private ParsedPlugins summaryPrinters = new ParsedPlugins(); void addPluginName(String name, boolean isAddPlugin) { - if (PluginFactory.isStepDefinitionReporterName(name)) { - stepDefinitionReporterNames.addName(name, isAddPlugin); - } else if (PluginFactory.isSummaryPrinterName(name)) { - summaryPrinterNames.addName(name, isAddPlugin); - } else if (PluginFactory.isFormatterName(name)) { - formatterNames.addName(name, isAddPlugin); + PluginOption pluginOption = PluginOption.parse(name); + if (pluginOption.isSummaryPrinter()) { + summaryPrinters.addName(pluginOption, isAddPlugin); + } else if (pluginOption.isFormatter()) { + formatters.addName(pluginOption, isAddPlugin); } else { throw new CucumberException("Unrecognized plugin: " + name); } } - void updatePluginFormatterNames(List pluginFormatterNames) { - formatterNames.updateNameList(pluginFormatterNames); - } - - void updatePluginStepDefinitionReporterNames(List pluginStepDefinitionReporterNames) { - stepDefinitionReporterNames.updateNameList(pluginStepDefinitionReporterNames); - } - - void updatePluginSummaryPrinterNames(List pluginSummaryPrinterNames) { - summaryPrinterNames.updateNameList(pluginSummaryPrinterNames); - } - - void addDefaultSummaryPrinterIfNotPresent() { - if (summaryPrinterNames.names.isEmpty()) { - summaryPrinterNames.addName("default_summary", false); + if (summaryPrinters.names.isEmpty()) { + addPluginName("summary", false); } } void addDefaultFormatterIfNotPresent() { - if (formatterNames.names.isEmpty()) { - formatterNames.addName("progress", false); + if (formatters.names.isEmpty()) { + addPluginName("progress", false); } } - } - private static class ParsedOptionNames { - private List names = new ArrayList<>(); - private boolean clobber = false; + void updateFormatters(List formatter) { + this.formatters.updateNameList(formatter); + } - void addName(String name, boolean isAddOption) { - names.add(name); - if (!isAddOption) { - clobber = true; - } + void updateSummaryPrinters(List pluginSummaryPrinterNames) { + summaryPrinters.updateNameList(pluginSummaryPrinterNames); } - void updateNameList(List nameList) { - if (!names.isEmpty()) { - if (clobber) { - nameList.clear(); + private static class ParsedPlugins { + private List names = new ArrayList<>(); + private boolean clobber = false; + + void addName(Options.Plugin name, boolean isAddOption) { + names.add(name); + if (!isAddOption) { + clobber = true; + } + } + + void updateNameList(List nameList) { + if (!names.isEmpty()) { + if (clobber) { + nameList.clear(); + } + nameList.addAll(names); } - nameList.addAll(names); } } } diff --git a/core/src/main/java/io/cucumber/core/options/RuntimeOptionsParser.java b/core/src/main/java/io/cucumber/core/options/RuntimeOptionsParser.java index 703c90c88f..dba3e8b63d 100644 --- a/core/src/main/java/io/cucumber/core/options/RuntimeOptionsParser.java +++ b/core/src/main/java/io/cucumber/core/options/RuntimeOptionsParser.java @@ -1,24 +1,23 @@ package io.cucumber.core.options; -import cucumber.api.SnippetType; -import cucumber.runtime.CucumberException; -import cucumber.runtime.order.PickleOrder; -import cucumber.runtime.order.StandardPickleOrders; -import cucumber.util.FixJava; -import cucumber.util.Mapper; import gherkin.GherkinDialect; import gherkin.GherkinDialectProvider; import gherkin.IGherkinDialectProvider; +import io.cucumber.core.exception.CucumberException; +import io.cucumber.core.feature.FeaturePath; +import io.cucumber.core.feature.FeatureWithLines; +import io.cucumber.core.feature.GluePath; +import io.cucumber.core.feature.RerunLoader; import io.cucumber.core.logging.Logger; import io.cucumber.core.logging.LoggerFactory; -import io.cucumber.core.model.FeaturePath; -import io.cucumber.core.model.FeatureWithLines; -import io.cucumber.core.model.GluePath; -import io.cucumber.core.model.RerunLoader; +import io.cucumber.core.order.PickleOrder; +import io.cucumber.core.order.StandardPickleOrders; +import io.cucumber.core.snippets.SnippetType; import io.cucumber.datatable.DataTable; +import java.io.BufferedReader; +import java.io.InputStream; import java.io.InputStreamReader; -import java.io.Reader; import java.net.URI; import java.util.ArrayList; import java.util.List; @@ -26,29 +25,18 @@ import java.util.ResourceBundle; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; -import static cucumber.util.FixJava.join; -import static cucumber.util.FixJava.map; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; +import static java.util.stream.Collectors.joining; final class RuntimeOptionsParser { private static final Logger log = LoggerFactory.getLogger(RuntimeOptionsParser.class); - static final String VERSION = ResourceBundle.getBundle("cucumber.version").getString("cucumber-jvm.version"); + static final String VERSION = ResourceBundle.getBundle("io.cucumber.core.version").getString("cucumber-jvm.version"); private static final Pattern RANDOM_AND_SEED_PATTERN = Pattern.compile("random(?::(\\d+))?"); - private static final Mapper QUOTE_MAPPER = new Mapper() { - @Override - public String map(String o) { - return '"' + o + '"'; - } - }; - private static final Mapper CODE_KEYWORD_MAPPER = new Mapper() { - @Override - public String map(String keyword) { - return keyword.replaceAll("[\\s',!]", ""); - } - }; // IMPORTANT! Make sure USAGE.txt is always uptodate if this class changes. private static final String USAGE_RESOURCE = "/io/cucumber/core/options/USAGE.txt"; static String usageText; @@ -96,7 +84,7 @@ RuntimeOptionsBuilder parse(List args) { parsedOptions.setMonochrome(!arg.startsWith("--no-")); } else if (arg.equals("--snippets")) { String nextArg = args.remove(0); - if("underscore".equals(nextArg)){ + if ("underscore".equals(nextArg)) { parsedOptions.setSnippetType(SnippetType.UNDERSCORE); } else if ("camelcase".equals(nextArg)) { parsedOptions.setSnippetType(SnippetType.CAMELCASE); @@ -107,10 +95,6 @@ RuntimeOptionsBuilder parse(List args) { String nextArg = args.remove(0); Pattern pattern = Pattern.compile(nextArg); parsedOptions.addNameFilter(pattern); - } else if (arg.startsWith("--junit,")) { - for (String parsedOption : arg.substring("--junit,".length()).split(",")) { - parsedOptions.addJunitOption(parsedOption); - } } else if (arg.equals("--wip") || arg.equals("-w")) { parsedOptions.setWip(true); } else if (arg.equals("--order")) { @@ -121,6 +105,9 @@ RuntimeOptionsBuilder parse(List args) { throw new CucumberException("--count must be > 0"); } parsedOptions.setCount(count); + } else if (arg.equals("--object-factory")) { + String objectFactoryClassName = args.remove(0); + parsedOptions.setObjectFactoryClass(CucumberPropertiesParser.parseObjectFactory(objectFactoryClassName)); } else if (arg.startsWith("-")) { printUsage(); throw new CucumberException("Unknown option: " + arg); @@ -167,9 +154,9 @@ private static void printUsage() { static void loadUsageTextIfNeeded() { if (usageText == null) { - try { - Reader reader = new InputStreamReader(FixJava.class.getResourceAsStream(USAGE_RESOURCE), "UTF-8"); - usageText = FixJava.readReader(reader); + InputStream usageResourceStream = RuntimeOptionsParser.class.getResourceAsStream(USAGE_RESOURCE); + try (BufferedReader br = new BufferedReader(new InputStreamReader(usageResourceStream, UTF_8))){ + usageText = br.lines().collect(joining(System.lineSeparator())); } catch (Exception e) { usageText = "Could not load usage text: " + e.toString(); } @@ -220,13 +207,16 @@ private static int printKeywordsFor(GherkinDialect dialect) { private static void addCodeKeywordRow(List> table, String key, List keywords) { List codeKeywordList = new ArrayList<>(keywords); codeKeywordList.remove("* "); - addKeywordRow(table, key + " (code)", map(codeKeywordList, CODE_KEYWORD_MAPPER)); + + List codeWords = codeKeywordList.stream() + .map(keyword -> keyword.replaceAll("[\\s',!]", "")) + .collect(Collectors.toList()); + + addKeywordRow(table, key + " (code)", codeWords); } private static void addKeywordRow(List> table, String key, List keywords) { - List cells = asList(key, join(map(keywords, QUOTE_MAPPER), ", ")); - table.add(cells); + table.add(asList(key, keywords.stream().map(o -> '"' + o + '"').collect(joining(", ")))); } - } diff --git a/core/src/main/java/cucumber/runtime/Shellwords.java b/core/src/main/java/io/cucumber/core/options/ShellWords.java similarity index 72% rename from core/src/main/java/cucumber/runtime/Shellwords.java rename to core/src/main/java/io/cucumber/core/options/ShellWords.java index 5835b9078b..c6583e49c3 100644 --- a/core/src/main/java/cucumber/runtime/Shellwords.java +++ b/core/src/main/java/io/cucumber/core/options/ShellWords.java @@ -1,18 +1,18 @@ -package cucumber.runtime; +package io.cucumber.core.options; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -public class Shellwords { +class ShellWords { private static final Pattern SHELLWORDS_PATTERN = Pattern.compile("[^\\s'\"]+|[']([^']*)[']|[\"]([^\"]*)[\"]"); - private Shellwords() { + private ShellWords() { } - public static List parse(String cmdline) { - List matchList = new ArrayList(); + static List parse(String cmdline) { + List matchList = new ArrayList<>(); Matcher shellwordsMatcher = SHELLWORDS_PATTERN.matcher(cmdline); while (shellwordsMatcher.find()) { if (shellwordsMatcher.group(1) != null) { @@ -20,8 +20,8 @@ public static List parse(String cmdline) { } else { String shellword = shellwordsMatcher.group(); if (shellword.startsWith("\"") - && shellword.endsWith("\"") - && shellword.length() > 2) { + && shellword.endsWith("\"") + && shellword.length() > 2) { shellword = shellword.substring(1, shellword.length() - 1); } matchList.add(shellword); diff --git a/core/src/main/java/cucumber/runtime/order/PickleOrder.java b/core/src/main/java/io/cucumber/core/order/PickleOrder.java similarity index 83% rename from core/src/main/java/cucumber/runtime/order/PickleOrder.java rename to core/src/main/java/io/cucumber/core/order/PickleOrder.java index db0f282a8a..6b1268e6ab 100644 --- a/core/src/main/java/cucumber/runtime/order/PickleOrder.java +++ b/core/src/main/java/io/cucumber/core/order/PickleOrder.java @@ -1,4 +1,4 @@ -package cucumber.runtime.order; +package io.cucumber.core.order; import java.util.List; diff --git a/core/src/main/java/cucumber/runtime/order/StandardPickleOrders.java b/core/src/main/java/io/cucumber/core/order/StandardPickleOrders.java similarity index 97% rename from core/src/main/java/cucumber/runtime/order/StandardPickleOrders.java rename to core/src/main/java/io/cucumber/core/order/StandardPickleOrders.java index 21be80004c..1bd662f92f 100644 --- a/core/src/main/java/cucumber/runtime/order/StandardPickleOrders.java +++ b/core/src/main/java/io/cucumber/core/order/StandardPickleOrders.java @@ -1,4 +1,4 @@ -package cucumber.runtime.order; +package io.cucumber.core.order; import gherkin.events.PickleEvent; diff --git a/core/src/main/java/io/cucumber/core/plugin/AnsiEscapes.java b/core/src/main/java/io/cucumber/core/plugin/AnsiEscapes.java new file mode 100644 index 0000000000..eeb675e8c2 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/plugin/AnsiEscapes.java @@ -0,0 +1,48 @@ +package io.cucumber.core.plugin; + +final class AnsiEscapes { + private static final char ESC = 27; + private static final char BRACKET = '['; + + static AnsiEscapes RESET = color(0); + static AnsiEscapes BLACK = color(30); + static AnsiEscapes RED = color(31); + static AnsiEscapes GREEN = color(32); + static AnsiEscapes YELLOW = color(33); + static AnsiEscapes BLUE = color(34); + static AnsiEscapes MAGENTA = color(35); + static AnsiEscapes CYAN = color(36); + static AnsiEscapes WHITE = color(37); + static AnsiEscapes DEFAULT = color(9); + static AnsiEscapes GREY = color(90); + static AnsiEscapes INTENSITY_BOLD = color(1); + + private static AnsiEscapes color(int code) { + return new AnsiEscapes(code + "m"); + } + + static AnsiEscapes up(int count) { + return new AnsiEscapes(count + "A"); + } + + private final String value; + + private AnsiEscapes(String value) { + this.value = value; + } + + void appendTo(NiceAppendable a) { + a.append(ESC).append(BRACKET).append(value); + } + + void appendTo(StringBuilder a) { + a.append(ESC).append(BRACKET).append(value); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + appendTo(sb); + return sb.toString(); + } +} diff --git a/core/src/main/java/cucumber/runtime/formatter/AnsiFormats.java b/core/src/main/java/io/cucumber/core/plugin/AnsiFormats.java similarity index 96% rename from core/src/main/java/cucumber/runtime/formatter/AnsiFormats.java rename to core/src/main/java/io/cucumber/core/plugin/AnsiFormats.java index cd8e30c9bc..3db1b520c8 100644 --- a/core/src/main/java/cucumber/runtime/formatter/AnsiFormats.java +++ b/core/src/main/java/io/cucumber/core/plugin/AnsiFormats.java @@ -1,6 +1,4 @@ -package cucumber.runtime.formatter; - -import cucumber.api.formatter.AnsiEscapes; +package io.cucumber.core.plugin; import java.util.HashMap; import java.util.Map; diff --git a/core/src/main/java/cucumber/api/event/CanonicalEventOrder.java b/core/src/main/java/io/cucumber/core/plugin/CanonicalEventOrder.java similarity index 70% rename from core/src/main/java/cucumber/api/event/CanonicalEventOrder.java rename to core/src/main/java/io/cucumber/core/plugin/CanonicalEventOrder.java index 240fcf3d8c..dd78fe5858 100644 --- a/core/src/main/java/cucumber/api/event/CanonicalEventOrder.java +++ b/core/src/main/java/io/cucumber/core/plugin/CanonicalEventOrder.java @@ -1,10 +1,42 @@ -package cucumber.api.event; +package io.cucumber.core.plugin; + +import io.cucumber.core.event.Event; +import io.cucumber.core.event.SnippetsSuggestedEvent; +import io.cucumber.core.event.StepDefinedEvent; +import io.cucumber.core.event.TestCaseEvent; +import io.cucumber.core.event.TestRunFinished; +import io.cucumber.core.event.TestRunStarted; +import io.cucumber.core.event.TestSourceRead; import java.util.Comparator; import java.util.List; import static java.util.Arrays.asList; +/** + * When pickles are executed in parallel events can be + * produced with a partial ordering. + *

+ * The canonical order is the order in which these events + * would have been generated had cucumber executed these + * pickles in a serial fashion. + *

+ * In canonical order events are first ordered by type: + *

    + *
  1. TestRunStarted + *
  2. TestSourceRead + *
  3. SnippetsSuggestedEvent + *
  4. TestCaseEvent + *
  5. TestRunFinished + *
+ *

+ * Then TestCaseEvents are ordered by + *

    + *
  1. uri + *
  2. line + *
  3. timestamp + *
+ */ final class CanonicalEventOrder implements Comparator { private static final FixedEventOrderComparator fixedOrder = new FixedEventOrderComparator(); @@ -27,8 +59,7 @@ public int compare(Event a, Event b) { private static final class FixedEventOrderComparator implements Comparator { private final List> fixedOrder = asList( - (Class) - TestRunStarted.class, + TestRunStarted.class, TestSourceRead.class, SnippetsSuggestedEvent.class, StepDefinedEvent.class, @@ -73,7 +104,7 @@ public int compare(TestCaseEvent a, TestCaseEvent b) { return line; } - return Long.compare(a.getTimeStamp(), b.getTimeStamp()); + return a.getInstant().compareTo(b.getInstant()); } } } diff --git a/core/src/main/java/cucumber/runner/CanonicalOrderEventPublisher.java b/core/src/main/java/io/cucumber/core/plugin/CanonicalOrderEventPublisher.java similarity index 50% rename from core/src/main/java/cucumber/runner/CanonicalOrderEventPublisher.java rename to core/src/main/java/io/cucumber/core/plugin/CanonicalOrderEventPublisher.java index 712df8077c..8562b027d5 100644 --- a/core/src/main/java/cucumber/runner/CanonicalOrderEventPublisher.java +++ b/core/src/main/java/io/cucumber/core/plugin/CanonicalOrderEventPublisher.java @@ -1,20 +1,20 @@ -package cucumber.runner; +package io.cucumber.core.plugin; -import cucumber.api.event.Event; -import cucumber.api.event.TestRunFinished; +import io.cucumber.core.event.Event; +import io.cucumber.core.event.TestRunFinished; +import io.cucumber.core.eventbus.AbstractEventPublisher; -import java.util.Collections; import java.util.LinkedList; import java.util.List; -public class CanonicalOrderEventPublisher extends AbstractEventPublisher { +final class CanonicalOrderEventPublisher extends AbstractEventPublisher { private final List queue = new LinkedList<>(); public void handle(final Event event) { queue.add(event); if (event instanceof TestRunFinished) { - Collections.sort(queue, Event.CANONICAL_ORDER); + queue.sort(new CanonicalEventOrder()); sendAll(queue); queue.clear(); } diff --git a/core/src/main/java/cucumber/api/formatter/ColorAware.java b/core/src/main/java/io/cucumber/core/plugin/ColorAware.java similarity index 81% rename from core/src/main/java/cucumber/api/formatter/ColorAware.java rename to core/src/main/java/io/cucumber/core/plugin/ColorAware.java index 1bbce1521c..ec0d626ff5 100644 --- a/core/src/main/java/cucumber/api/formatter/ColorAware.java +++ b/core/src/main/java/io/cucumber/core/plugin/ColorAware.java @@ -1,10 +1,11 @@ -package cucumber.api.formatter; +package io.cucumber.core.plugin; -import cucumber.api.Plugin; +import org.apiguardian.api.API; /** * Interface for Plugins that use ANSI escape codes to print coloured output. */ +@API(status = API.Status.STABLE) public interface ColorAware extends Plugin { /** * When set to monochrome the plugin should not use colored output. diff --git a/core/src/main/java/cucumber/api/event/ConcurrentEventListener.java b/core/src/main/java/io/cucumber/core/plugin/ConcurrentEventListener.java similarity index 75% rename from core/src/main/java/cucumber/api/event/ConcurrentEventListener.java rename to core/src/main/java/io/cucumber/core/plugin/ConcurrentEventListener.java index 8a01204c8a..c3118a2151 100644 --- a/core/src/main/java/cucumber/api/event/ConcurrentEventListener.java +++ b/core/src/main/java/io/cucumber/core/plugin/ConcurrentEventListener.java @@ -1,6 +1,9 @@ -package cucumber.api.event; +package io.cucumber.core.plugin; -import cucumber.api.Plugin; +import io.cucumber.core.event.Event; +import io.cucumber.core.event.EventPublisher; +import io.cucumber.core.event.TestCase; +import org.apiguardian.api.API; /** * Listens to pickle execution events. Can be used to @@ -8,7 +11,7 @@ *

* When cucumber executes test in parallel or in a framework * that supports parallel execution (e.g. JUnit or TestNG) - * {@link cucumber.api.TestCase} events from different + * {@link TestCase} events from different * pickles may interleave. *

* This interface marks an {@link EventListener} as capable of @@ -29,10 +32,11 @@ * * @see Event */ +@API(status = API.Status.STABLE) public interface ConcurrentEventListener extends Plugin { /** - * Set the event publisher. The formatter can register event listeners with the publisher. + * Set the event publisher. The plugin can register event listeners with the publisher. * * @param publisher the event publisher */ diff --git a/core/src/main/java/cucumber/runtime/formatter/DefaultSummaryPrinter.java b/core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java similarity index 78% rename from core/src/main/java/cucumber/runtime/formatter/DefaultSummaryPrinter.java rename to core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java index e8457edc7e..6cd155ee0f 100644 --- a/core/src/main/java/cucumber/runtime/formatter/DefaultSummaryPrinter.java +++ b/core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java @@ -1,18 +1,13 @@ -package cucumber.runtime.formatter; +package io.cucumber.core.plugin; -import cucumber.api.SummaryPrinter; -import cucumber.api.event.EventHandler; -import cucumber.api.event.EventListener; -import cucumber.api.event.EventPublisher; -import cucumber.api.event.TestRunFinished; -import cucumber.api.formatter.ColorAware; -import cucumber.api.formatter.StrictAware; -import cucumber.runtime.UndefinedStepsTracker; +import io.cucumber.core.event.EventHandler; +import io.cucumber.core.event.EventPublisher; +import io.cucumber.core.event.TestRunFinished; import java.io.PrintStream; import java.util.List; -class DefaultSummaryPrinter implements SummaryPrinter, ColorAware, StrictAware, EventListener { +public final class DefaultSummaryPrinter implements SummaryPrinter, ColorAware, StrictAware, EventListener { private final Stats stats = new Stats(); private final UndefinedStepsTracker undefinedStepsTracker = new UndefinedStepsTracker(); diff --git a/core/src/main/java/cucumber/api/event/EventListener.java b/core/src/main/java/io/cucumber/core/plugin/EventListener.java similarity index 63% rename from core/src/main/java/cucumber/api/event/EventListener.java rename to core/src/main/java/io/cucumber/core/plugin/EventListener.java index 614a62d497..f23b21fb38 100644 --- a/core/src/main/java/cucumber/api/event/EventListener.java +++ b/core/src/main/java/io/cucumber/core/plugin/EventListener.java @@ -1,6 +1,8 @@ -package cucumber.api.event; +package io.cucumber.core.plugin; -import cucumber.api.Plugin; +import io.cucumber.core.event.Event; +import io.cucumber.core.event.EventPublisher; +import org.apiguardian.api.API; /** * Listens to pickle execution events. Can be used to @@ -8,13 +10,14 @@ *

* When cucumber executes test in parallel or in a framework * that supports parallel execution (e.g. JUnit or TestNG) - * {@link cucumber.api.event.Event}s are stored and published - * in @{@link Event#CANONICAL_ORDER} after the test run has + * {@link Event}s are stored and published + * in @{@link io.cucumber.core.plugin.CanonicalEventOrder} after the test run has * completed. * * @see Event * @see ConcurrentEventListener */ +@API(status = API.Status.STABLE) public interface EventListener extends Plugin { /** diff --git a/core/src/main/java/io/cucumber/core/plugin/Format.java b/core/src/main/java/io/cucumber/core/plugin/Format.java new file mode 100644 index 0000000000..272c39c075 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/plugin/Format.java @@ -0,0 +1,5 @@ +package io.cucumber.core.plugin; + +interface Format { + String text(String text); +} diff --git a/core/src/main/java/io/cucumber/core/plugin/Formats.java b/core/src/main/java/io/cucumber/core/plugin/Formats.java new file mode 100644 index 0000000000..3d4e046928 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/plugin/Formats.java @@ -0,0 +1,7 @@ +package io.cucumber.core.plugin; + +interface Formats { + Format get(String key); + + String up(int n); +} diff --git a/core/src/main/java/cucumber/runtime/formatter/HTMLFormatter.java b/core/src/main/java/io/cucumber/core/plugin/HTMLFormatter.java similarity index 76% rename from core/src/main/java/cucumber/runtime/formatter/HTMLFormatter.java rename to core/src/main/java/io/cucumber/core/plugin/HTMLFormatter.java index 66c94a1c38..8f7c599cc3 100644 --- a/core/src/main/java/cucumber/runtime/formatter/HTMLFormatter.java +++ b/core/src/main/java/io/cucumber/core/plugin/HTMLFormatter.java @@ -1,21 +1,5 @@ -package cucumber.runtime.formatter; - -import cucumber.api.HookTestStep; -import cucumber.api.PickleStepTestStep; -import cucumber.api.Result; -import cucumber.api.TestCase; -import cucumber.api.event.EmbedEvent; -import cucumber.api.event.EventHandler; -import cucumber.api.event.EventListener; -import cucumber.api.event.EventPublisher; -import cucumber.api.event.TestCaseStarted; -import cucumber.api.event.TestRunFinished; -import cucumber.api.event.TestSourceRead; -import cucumber.api.event.TestStepFinished; -import cucumber.api.event.TestStepStarted; -import cucumber.api.event.WriteEvent; -import cucumber.api.formatter.NiceAppendable; -import cucumber.runtime.CucumberException; +package io.cucumber.core.plugin; + import gherkin.ast.Background; import gherkin.ast.DataTable; import gherkin.ast.DocString; @@ -35,7 +19,21 @@ import gherkin.pickles.PickleRow; import gherkin.pickles.PickleString; import gherkin.pickles.PickleTable; -import gherkin.pickles.PickleTag; +import io.cucumber.core.event.EmbedEvent; +import io.cucumber.core.event.EventHandler; +import io.cucumber.core.event.EventPublisher; +import io.cucumber.core.event.HookTestStep; +import io.cucumber.core.event.HookType; +import io.cucumber.core.event.PickleStepTestStep; +import io.cucumber.core.event.Result; +import io.cucumber.core.event.TestCase; +import io.cucumber.core.event.TestCaseStarted; +import io.cucumber.core.event.TestRunFinished; +import io.cucumber.core.event.TestSourceRead; +import io.cucumber.core.event.TestStepFinished; +import io.cucumber.core.event.TestStepStarted; +import io.cucumber.core.event.WriteEvent; +import io.cucumber.core.exception.CucumberException; import java.io.Closeable; import java.io.File; @@ -43,22 +41,27 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.StringWriter; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -final class HTMLFormatter implements EventListener { +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Locale.ROOT; + +public final class HTMLFormatter implements EventListener { private static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); private static final String JS_FORMATTER_VAR = "formatter"; private static final String JS_REPORT_FILENAME = "report.js"; private static final String[] TEXT_ASSETS = new String[]{ - "/io/cucumber/formatter/html/formatter.js", - "/io/cucumber/formatter/html/index.html", - "/io/cucumber/formatter/html/jquery-1.8.2.min.js", - "/io/cucumber/formatter/html/style.css" - }; + "/io/cucumber/core/plugin/html/formatter.js", + "/io/cucumber/core/plugin/html/index.html", + "/io/cucumber/core/plugin/html/jquery-1.8.2.min.js", + "/io/cucumber/core/plugin/html/style.css" + }; private static final Map MIME_TYPES_EXTENSIONS = new HashMap() { { put("image/bmp", "bmp"); @@ -87,7 +90,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); @@ -146,20 +149,20 @@ public void setEventPublisher(EventPublisher publisher) { } private void handleTestSourceRead(TestSourceRead event) { - testSources.addTestSourceReadEvent(event.uri, event); + testSources.addTestSourceReadEvent(event.getUri(), event); } private void handleTestCaseStarted(TestCaseStarted event) { if (firstFeature) { jsOut.append("$(document).ready(function() {").append("var ") - .append(JS_FORMATTER_VAR).append(" = new CucumberHTML.DOMFormatter($('.cucumber-report'));"); + .append(JS_FORMATTER_VAR).append(" = new CucumberHTML.DOMFormatter($('.cucumber-report'));"); firstFeature = false; } - handleStartOfFeature(event.testCase); - handleScenarioOutline(event.testCase); - currentTestCaseMap = createTestCase(event.testCase); - if (testSources.hasBackground(currentFeatureFile, event.testCase.getLine())) { - jsFunctionCall("background", createBackground(event.testCase)); + handleStartOfFeature(event.getTestCase()); + handleScenarioOutline(event.getTestCase()); + currentTestCaseMap = createTestCase(event.getTestCase()); + if (testSources.hasBackground(currentFeatureFile, event.getTestCase().getLine())) { + jsFunctionCall("background", createBackground(event.getTestCase())); } else { jsFunctionCall("scenario", currentTestCaseMap); currentTestCaseMap = null; @@ -167,46 +170,62 @@ private void handleTestCaseStarted(TestCaseStarted event) { } private void handleTestStepStarted(TestStepStarted event) { - if (event.testStep instanceof PickleStepTestStep) { - PickleStepTestStep testStep = (PickleStepTestStep) event.testStep; + if (event.getTestStep() instanceof PickleStepTestStep) { + PickleStepTestStep testStep = (PickleStepTestStep) event.getTestStep(); if (isFirstStepAfterBackground(testStep)) { jsFunctionCall("scenario", currentTestCaseMap); currentTestCaseMap = null; } jsFunctionCall("step", createTestStep(testStep)); - jsFunctionCall("match", createMatchMap((PickleStepTestStep) event.testStep)); + jsFunctionCall("match", createMatchMap((PickleStepTestStep) event.getTestStep())); } } private void handleTestStepFinished(TestStepFinished event) { - if (event.testStep instanceof PickleStepTestStep) { - jsFunctionCall("result", createResultMap(event.result)); - } else if(event.testStep instanceof HookTestStep) { - HookTestStep hookTestStep = (HookTestStep) event.testStep; - jsFunctionCall(hookTestStep.getHookType().toString(), createResultMap(event.result)); + if (event.getTestStep() instanceof PickleStepTestStep) { + jsFunctionCall("result", createResultMap(event.getResult())); + } else if (event.getTestStep() instanceof HookTestStep) { + HookTestStep hookTestStep = (HookTestStep) event.getTestStep(); + jsFunctionCall(getFunctionName(hookTestStep), createResultMap(event.getResult())); } else { throw new IllegalStateException(); } } + private String getFunctionName(HookTestStep hookTestStep) { + HookType hookType = hookTestStep.getHookType(); + switch (hookType) { + case BEFORE: + return "before"; + case AFTER: + return "after"; + case BEFORE_STEP: + return "beforestep"; + case AFTER_STEP: + return "afterstep"; + default: + throw new IllegalArgumentException(hookType.name()); + } + } + private void handleEmbed(EmbedEvent event) { - String mimeType = event.mimeType; - if(mimeType.startsWith("text/")) { - // just pass straight to the formatter to output in the html - jsFunctionCall("embedding", mimeType, new String(event.data), event.name); + String mimeType = event.getMimeType(); + if (mimeType.startsWith("text/")) { + // just pass straight to the plugin to output in the html + jsFunctionCall("embedding", mimeType, new String(event.getData()), event.getName()); } else { // Creating a file instead of using data urls to not clutter the js file String extension = MIME_TYPES_EXTENSIONS.get(mimeType); if (extension != null) { StringBuilder fileName = new StringBuilder("embedded").append(embeddedIndex++).append(".").append(extension); - writeBytesToURL(event.data, toUrl(fileName.toString())); - jsFunctionCall("embedding", mimeType, fileName, event.name); + writeBytesToURL(event.getData(), toUrl(fileName.toString())); + jsFunctionCall("embedding", mimeType, fileName, event.getName()); } } } private void handleWrite(WriteEvent event) { - jsFunctionCall("write", event.text); + jsFunctionCall("write", event.getText()); } private void finishReport() { @@ -226,7 +245,7 @@ private void handleStartOfFeature(TestCase testCase) { } private Map createFeature(TestCase testCase) { - Map featureMap = new HashMap(); + Map featureMap = new HashMap<>(); Feature feature = testSources.getFeature(testCase.getUri()); if (feature != null) { featureMap.put("keyword", feature.getKeyword()); @@ -240,9 +259,9 @@ private Map createFeature(TestCase testCase) { } private List> createTagList(List tags) { - List> tagList = new ArrayList>(); + List> tagList = new ArrayList<>(); for (Tag tag : tags) { - Map tagMap = new HashMap(); + Map tagMap = new HashMap<>(); tagMap.put("name", tag.getName()); tagList.add(tagMap); } @@ -252,13 +271,13 @@ private List> createTagList(List tags) { private void handleScenarioOutline(TestCase testCase) { TestSourcesModel.AstNode astNode = testSources.getAstNode(currentFeatureFile, testCase.getLine()); if (TestSourcesModel.isScenarioOutlineScenario(astNode)) { - ScenarioOutline scenarioOutline = (ScenarioOutline)TestSourcesModel.getScenarioDefinition(astNode); + ScenarioOutline scenarioOutline = (ScenarioOutline) TestSourcesModel.getScenarioDefinition(astNode); if (currentScenarioOutline == null || !currentScenarioOutline.equals(scenarioOutline)) { currentScenarioOutline = scenarioOutline; jsFunctionCall("scenarioOutline", createScenarioOutline(currentScenarioOutline)); addOutlineStepsToReport(scenarioOutline); } - Examples examples = (Examples)astNode.parent.node; + Examples examples = (Examples) astNode.parent.node; if (currentExamples == null || !currentExamples.equals(examples)) { currentExamples = examples; jsFunctionCall("examples", createExamples(currentExamples)); @@ -270,7 +289,7 @@ private void handleScenarioOutline(TestCase testCase) { } private Map createScenarioOutline(ScenarioOutline scenarioOutline) { - Map scenarioOutlineMap = new HashMap(); + Map scenarioOutlineMap = new HashMap<>(); scenarioOutlineMap.put("name", scenarioOutline.getName()); scenarioOutlineMap.put("keyword", scenarioOutline.getKeyword()); scenarioOutlineMap.put("description", scenarioOutline.getDescription() != null ? scenarioOutline.getDescription() : ""); @@ -282,15 +301,15 @@ private Map createScenarioOutline(ScenarioOutline scenarioOutlin private void addOutlineStepsToReport(ScenarioOutline scenarioOutline) { for (Step step : scenarioOutline.getSteps()) { - Map stepMap = new HashMap(); + Map stepMap = new HashMap<>(); stepMap.put("name", step.getText()); stepMap.put("keyword", step.getKeyword()); if (step.getArgument() != null) { Node argument = step.getArgument(); if (argument instanceof DocString) { - stepMap.put("doc_string", createDocStringMap((DocString)argument)); + stepMap.put("doc_string", createDocStringMap((DocString) argument)); } else if (argument instanceof DataTable) { - stepMap.put("rows", createDataTableList((DataTable)argument)); + stepMap.put("rows", createDataTableList((DataTable) argument)); } } jsFunctionCall("step", stepMap); @@ -298,13 +317,13 @@ private void addOutlineStepsToReport(ScenarioOutline scenarioOutline) { } private Map createDocStringMap(DocString docString) { - Map docStringMap = new HashMap(); + Map docStringMap = new HashMap<>(); docStringMap.put("value", docString.getContent()); return docStringMap; } private List> createDataTableList(DataTable dataTable) { - List> rowList = new ArrayList>(); + List> rowList = new ArrayList<>(); for (TableRow row : dataTable.getRows()) { rowList.add(createRowMap(row)); } @@ -312,13 +331,13 @@ private List> createDataTableList(DataTable dataTable) { } private Map createRowMap(TableRow row) { - Map rowMap = new HashMap(); + Map rowMap = new HashMap<>(); rowMap.put("cells", createCellList(row)); return rowMap; } private List createCellList(TableRow row) { - List cells = new ArrayList(); + List cells = new ArrayList<>(); for (TableCell cell : row.getCells()) { cells.add(cell.getValue()); } @@ -326,11 +345,11 @@ private List createCellList(TableRow row) { } private Map createExamples(Examples examples) { - Map examplesMap = new HashMap(); + Map examplesMap = new HashMap<>(); examplesMap.put("name", examples.getName()); examplesMap.put("keyword", examples.getKeyword()); examplesMap.put("description", examples.getDescription() != null ? examples.getDescription() : ""); - List> rowList = new ArrayList>(); + List> rowList = new ArrayList<>(); rowList.add(createRowMap(examples.getTableHeader())); for (TableRow row : examples.getTableBody()) { rowList.add(createRowMap(row)); @@ -343,7 +362,7 @@ private Map createExamples(Examples examples) { } private Map createTestCase(TestCase testCase) { - Map testCaseMap = new HashMap(); + Map testCaseMap = new HashMap<>(); testCaseMap.put("name", testCase.getName()); TestSourcesModel.AstNode astNode = testSources.getAstNode(currentFeatureFile, testCase.getLine()); if (astNode != null) { @@ -352,10 +371,10 @@ private Map createTestCase(TestCase testCase) { testCaseMap.put("description", scenarioDefinition.getDescription() != null ? scenarioDefinition.getDescription() : ""); } if (!testCase.getTags().isEmpty()) { - List> tagList = new ArrayList>(); - for (PickleTag tag : testCase.getTags()) { - Map tagMap = new HashMap(); - tagMap.put("name", tag.getName()); + List> tagList = new ArrayList<>(); + for (String tag : testCase.getTags()) { + Map tagMap = new HashMap<>(); + tagMap.put("name", tag); tagList.add(tagMap); } testCaseMap.put("tags", tagList); @@ -367,7 +386,7 @@ private Map createBackground(TestCase testCase) { TestSourcesModel.AstNode astNode = testSources.getAstNode(currentFeatureFile, testCase.getLine()); if (astNode != null) { Background background = TestSourcesModel.getBackgroundForTestCase(astNode); - Map testCaseMap = new HashMap(); + Map testCaseMap = new HashMap<>(); testCaseMap.put("name", background.getName()); testCaseMap.put("keyword", background.getKeyword()); testCaseMap.put("description", background.getDescription() != null ? background.getDescription() : ""); @@ -379,22 +398,20 @@ private Map createBackground(TestCase testCase) { private boolean isFirstStepAfterBackground(PickleStepTestStep testStep) { TestSourcesModel.AstNode astNode = testSources.getAstNode(currentFeatureFile, testStep.getStepLine()); if (astNode != null) { - if (currentTestCaseMap != null && !TestSourcesModel.isBackgroundStep(astNode)) { - return true; - } + return currentTestCaseMap != null && !TestSourcesModel.isBackgroundStep(astNode); } return false; } private Map createTestStep(PickleStepTestStep testStep) { - Map stepMap = new HashMap(); + Map stepMap = new HashMap<>(); stepMap.put("name", testStep.getStepText()); if (!testStep.getStepArgument().isEmpty()) { Argument argument = testStep.getStepArgument().get(0); if (argument instanceof PickleString) { - stepMap.put("doc_string", createDocStringMap((PickleString)argument)); + stepMap.put("doc_string", createDocStringMap((PickleString) argument)); } else if (argument instanceof PickleTable) { - stepMap.put("rows", createDataTableList((PickleTable)argument)); + stepMap.put("rows", createDataTableList((PickleTable) argument)); } } TestSourcesModel.AstNode astNode = testSources.getAstNode(currentFeatureFile, testStep.getStepLine()); @@ -407,13 +424,13 @@ private Map createTestStep(PickleStepTestStep testStep) { } private Map createDocStringMap(PickleString docString) { - Map docStringMap = new HashMap(); + Map docStringMap = new HashMap<>(); docStringMap.put("value", docString.getContent()); return docStringMap; } private List> createDataTableList(PickleTable dataTable) { - List> rowList = new ArrayList>(); + List> rowList = new ArrayList<>(); for (PickleRow row : dataTable.getRows()) { rowList.add(createRowMap(row)); } @@ -421,13 +438,13 @@ private List> createDataTableList(PickleTable dataTable) { } private Map createRowMap(PickleRow row) { - Map rowMap = new HashMap(); + Map rowMap = new HashMap<>(); rowMap.put("cells", createCellList(row)); return rowMap; } private List createCellList(PickleRow row) { - List cells = new ArrayList(); + List cells = new ArrayList<>(); for (PickleCell cell : row.getCells()) { cells.add(cell.getValue()); } @@ -435,7 +452,7 @@ private List createCellList(PickleRow row) { } private Map createMatchMap(PickleStepTestStep testStep) { - Map matchMap = new HashMap(); + Map matchMap = new HashMap<>(); String location = testStep.getCodeLocation(); if (location != null) { matchMap.put("location", location); @@ -444,10 +461,10 @@ private Map createMatchMap(PickleStepTestStep testStep) { } private Map createResultMap(Result result) { - Map resultMap = new HashMap(); - resultMap.put("status", result.getStatus().lowerCaseName()); - if (result.getErrorMessage() != null) { - resultMap.put("error_message", result.getErrorMessage()); + Map resultMap = new HashMap<>(); + resultMap.put("status", result.getStatus().name().toLowerCase(ROOT)); + if (result.getError() != null) { + resultMap.put("error_message", printStackTrace(result.getError())); } return resultMap; } @@ -479,11 +496,18 @@ private void copyReportFiles() { } } + private static String printStackTrace(Throwable error) { + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + error.printStackTrace(printWriter); + return stringWriter.toString(); + } + private URL toUrl(String fileName) { try { return new URL(htmlReportDir, fileName); } catch (IOException e) { - throw new CucumberException(e); + throw new CucumberException(e); } } @@ -517,7 +541,7 @@ private static void writeBytesToURL(byte[] buf, URL url) throws CucumberExceptio private static NiceAppendable createJsOut(URL htmlReportDir) { try { - return new NiceAppendable(new OutputStreamWriter(createReportFileOutputStream(new URL(htmlReportDir, JS_REPORT_FILENAME)), "UTF-8")); + return new NiceAppendable(new OutputStreamWriter(createReportFileOutputStream(new URL(htmlReportDir, JS_REPORT_FILENAME)), UTF_8)); } catch (IOException e) { throw new CucumberException(e); } diff --git a/core/src/main/java/cucumber/runtime/formatter/JSONFormatter.java b/core/src/main/java/io/cucumber/core/plugin/JSONFormatter.java similarity index 74% rename from core/src/main/java/cucumber/runtime/formatter/JSONFormatter.java rename to core/src/main/java/io/cucumber/core/plugin/JSONFormatter.java index 3a598d0e52..da5eb2a4bf 100644 --- a/core/src/main/java/cucumber/runtime/formatter/JSONFormatter.java +++ b/core/src/main/java/io/cucumber/core/plugin/JSONFormatter.java @@ -1,22 +1,21 @@ -package cucumber.runtime.formatter; - -import cucumber.api.HookTestStep; -import cucumber.api.HookType; -import cucumber.api.PickleStepTestStep; -import cucumber.api.Result; -import cucumber.api.TestCase; -import cucumber.api.TestStep; -import cucumber.api.event.EmbedEvent; -import cucumber.api.event.EventHandler; -import cucumber.api.event.EventListener; -import cucumber.api.event.EventPublisher; -import cucumber.api.event.TestCaseStarted; -import cucumber.api.event.TestRunFinished; -import cucumber.api.event.TestSourceRead; -import cucumber.api.event.TestStepFinished; -import cucumber.api.event.TestStepStarted; -import cucumber.api.event.WriteEvent; -import cucumber.api.formatter.NiceAppendable; +package io.cucumber.core.plugin; + +import io.cucumber.core.event.HookTestStep; +import io.cucumber.core.event.HookType; +import io.cucumber.core.event.PickleStepTestStep; +import io.cucumber.core.event.Result; +import io.cucumber.core.event.Status; +import io.cucumber.core.event.TestCase; +import io.cucumber.core.event.TestStep; +import io.cucumber.core.event.EmbedEvent; +import io.cucumber.core.event.EventHandler; +import io.cucumber.core.event.EventPublisher; +import io.cucumber.core.event.TestCaseStarted; +import io.cucumber.core.event.TestRunFinished; +import io.cucumber.core.event.TestSourceRead; +import io.cucumber.core.event.TestStepFinished; +import io.cucumber.core.event.TestStepStarted; +import io.cucumber.core.event.WriteEvent; import gherkin.ast.Background; import gherkin.ast.Feature; import gherkin.ast.ScenarioDefinition; @@ -29,25 +28,30 @@ import gherkin.pickles.PickleRow; import gherkin.pickles.PickleString; import gherkin.pickles.PickleTable; -import gherkin.pickles.PickleTag; -import java.text.SimpleDateFormat; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; -import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.TimeZone; -final class JSONFormatter implements EventListener { +import static java.util.Locale.ROOT; + +public final class JSONFormatter implements EventListener { + private static final String before = "before"; + private static final String after = "after"; private String currentFeatureFile; - private List> featureMaps = new ArrayList>(); + private List> featureMaps = new ArrayList<>(); private List> currentElementsList; private Map currentElementMap; private Map currentTestCaseMap; private List> currentStepsList; private Map currentStepOrHookMap; - private Map currentBeforeStepHookList = new HashMap(); + private Map currentBeforeStepHookList = new HashMap<>(); private final Gson gson = new GsonBuilder().setPrettyPrinting().create(); private final NiceAppendable out; private final TestSourcesModel testSources = new TestSourcesModel(); @@ -112,19 +116,19 @@ public void setEventPublisher(EventPublisher publisher) { } private void handleTestSourceRead(TestSourceRead event) { - testSources.addTestSourceReadEvent(event.uri, event); + testSources.addTestSourceReadEvent(event.getUri(), event); } private void handleTestCaseStarted(TestCaseStarted event) { - if (currentFeatureFile == null || !currentFeatureFile.equals(event.testCase.getUri())) { - currentFeatureFile = event.testCase.getUri(); - Map currentFeatureMap = createFeatureMap(event.testCase); + if (currentFeatureFile == null || !currentFeatureFile.equals(event.getTestCase().getUri())) { + currentFeatureFile = event.getTestCase().getUri(); + Map currentFeatureMap = createFeatureMap(event.getTestCase()); featureMaps.add(currentFeatureMap); currentElementsList = (List>) currentFeatureMap.get("elements"); } currentTestCaseMap = createTestCase(event); - if (testSources.hasBackground(currentFeatureFile, event.testCase.getLine())) { - currentElementMap = createBackground(event.testCase); + if (testSources.hasBackground(currentFeatureFile, event.getTestCase().getLine())) { + currentElementMap = createBackground(event.getTestCase()); currentElementsList.add(currentElementMap); } else { currentElementMap = currentTestCaseMap; @@ -134,21 +138,21 @@ private void handleTestCaseStarted(TestCaseStarted event) { } private void handleTestStepStarted(TestStepStarted event) { - if (event.testStep instanceof PickleStepTestStep) { - PickleStepTestStep testStep = (PickleStepTestStep) event.testStep; + if (event.getTestStep() instanceof PickleStepTestStep) { + PickleStepTestStep testStep = (PickleStepTestStep) event.getTestStep(); if (isFirstStepAfterBackground(testStep)) { currentElementMap = currentTestCaseMap; currentStepsList = (List>) currentElementMap.get("steps"); } currentStepOrHookMap = createTestStep(testStep); //add beforeSteps list to current step - if (currentBeforeStepHookList.containsKey(HookType.Before.toString())) { - currentStepOrHookMap.put(HookType.Before.toString(), currentBeforeStepHookList.get(HookType.Before.toString())); + if (currentBeforeStepHookList.containsKey(before)) { + currentStepOrHookMap.put(before, currentBeforeStepHookList.get(before)); currentBeforeStepHookList.clear(); } currentStepsList.add(currentStepOrHookMap); - } else if(event.testStep instanceof HookTestStep) { - HookTestStep hookTestStep = (HookTestStep) event.testStep; + } else if(event.getTestStep() instanceof HookTestStep) { + HookTestStep hookTestStep = (HookTestStep) event.getTestStep(); currentStepOrHookMap = createHookStep(hookTestStep); addHookStepToTestCaseMap(currentStepOrHookMap, hookTestStep.getHookType()); } else { @@ -157,16 +161,16 @@ private void handleTestStepStarted(TestStepStarted event) { } private void handleWrite(WriteEvent event) { - addOutputToHookMap(event.text); + addOutputToHookMap(event.getText()); } private void handleEmbed(EmbedEvent event) { - addEmbeddingToHookMap(event.data, event.mimeType, event.name); + addEmbeddingToHookMap(event.getData(), event.getMimeType(), event.getName()); } private void handleTestStepFinished(TestStepFinished event) { - currentStepOrHookMap.put("match", createMatchMap(event.testStep, event.result)); - currentStepOrHookMap.put("result", createResultMap(event.result)); + currentStepOrHookMap.put("match", createMatchMap(event.getTestStep(), event.getResult())); + currentStepOrHookMap.put("result", createResultMap(event.getResult())); } private void finishReport() { @@ -175,7 +179,7 @@ private void finishReport() { } private Map createFeatureMap(TestCase testCase) { - Map featureMap = new HashMap(); + Map featureMap = new HashMap<>(); featureMap.put("uri", testCase.getUri()); featureMap.put("elements", new ArrayList>()); Feature feature = testSources.getFeature(testCase.getUri()); @@ -192,9 +196,9 @@ private Map createFeatureMap(TestCase testCase) { } private Map createTestCase(TestCaseStarted event) { - Map testCaseMap = new HashMap(); + Map testCaseMap = new HashMap<>(); - testCaseMap.put("start_timestamp", getDateTimeFromTimeStamp(event.getTimeStampMillis())); + testCaseMap.put("start_timestamp", getDateTimeFromTimeStamp(event.getInstant())); TestCase testCase = event.getTestCase(); @@ -210,10 +214,10 @@ private Map createTestCase(TestCaseStarted event) { } testCaseMap.put("steps", new ArrayList>()); if (!testCase.getTags().isEmpty()) { - List> tagList = new ArrayList>(); - for (PickleTag tag : testCase.getTags()) { - Map tagMap = new HashMap(); - tagMap.put("name", tag.getName()); + List> tagList = new ArrayList<>(); + for (String tag : testCase.getTags()) { + Map tagMap = new HashMap<>(); + tagMap.put("name", tag); tagList.add(tagMap); } testCaseMap.put("tags", tagList); @@ -225,7 +229,7 @@ private Map createBackground(TestCase testCase) { TestSourcesModel.AstNode astNode = testSources.getAstNode(currentFeatureFile, testCase.getLine()); if (astNode != null) { Background background = TestSourcesModel.getBackgroundForTestCase(astNode); - Map testCaseMap = new HashMap(); + Map testCaseMap = new HashMap<>(); testCaseMap.put("name", background.getName()); testCaseMap.put("line", background.getLocation().getLine()); testCaseMap.put("type", "background"); @@ -239,16 +243,14 @@ private Map createBackground(TestCase testCase) { private boolean isFirstStepAfterBackground(PickleStepTestStep testStep) { TestSourcesModel.AstNode astNode = testSources.getAstNode(currentFeatureFile, testStep.getStepLine()); - if (astNode != null) { - if (currentElementMap != currentTestCaseMap && !TestSourcesModel.isBackgroundStep(astNode)) { - return true; - } + if (astNode == null) { + return false; } - return false; + return currentElementMap != currentTestCaseMap && !TestSourcesModel.isBackgroundStep(astNode); } private Map createTestStep(PickleStepTestStep testStep) { - Map stepMap = new HashMap(); + Map stepMap = new HashMap<>(); stepMap.put("name", testStep.getStepText()); stepMap.put("line", testStep.getStepLine()); TestSourcesModel.AstNode astNode = testSources.getAstNode(currentFeatureFile, testStep.getStepLine()); @@ -269,7 +271,7 @@ private Map createTestStep(PickleStepTestStep testStep) { } private Map createDocStringMap(Argument argument) { - Map docStringMap = new HashMap(); + Map docStringMap = new HashMap<>(); PickleString docString = ((PickleString)argument); docStringMap.put("value", docString.getContent()); docStringMap.put("line", docString.getLocation().getLine()); @@ -278,9 +280,9 @@ private Map createDocStringMap(Argument argument) { } private List> createDataTableList(Argument argument) { - List> rowList = new ArrayList>(); + List> rowList = new ArrayList<>(); for (PickleRow row : ((PickleTable)argument).getRows()) { - Map rowMap = new HashMap(); + Map rowMap = new HashMap<>(); rowMap.put("cells", createCellList(row)); rowList.add(rowMap); } @@ -288,7 +290,7 @@ private List> createDataTableList(Argument argument) { } private List createCellList(PickleRow row) { - List cells = new ArrayList(); + List cells = new ArrayList<>(); for (PickleCell cell : row.getCells()) { cells.add(cell.getValue()); } @@ -296,33 +298,32 @@ private List createCellList(PickleRow row) { } private Map createHookStep(HookTestStep hookTestStep) { - return new HashMap(); + return new HashMap<>(); } private void addHookStepToTestCaseMap(Map currentStepOrHookMap, HookType hookType) { String hookName; - if (hookType.toString().contains("after")) - hookName = "after"; + if (hookType == HookType.AFTER || hookType == HookType.AFTER_STEP) + hookName = after; else - hookName = "before"; - + hookName = before; Map mapToAddTo; switch (hookType) { - case Before: + case BEFORE: mapToAddTo = currentTestCaseMap; break; - case After: + case AFTER: mapToAddTo = currentTestCaseMap; break; - case BeforeStep: + case BEFORE_STEP: mapToAddTo = currentBeforeStepHookList; break; - case AfterStep: + case AFTER_STEP: mapToAddTo = currentStepsList.get(currentStepsList.size() - 1); break; - default: - mapToAddTo = currentTestCaseMap; + default: + mapToAddTo = currentTestCaseMap; } if (!mapToAddTo.containsKey(hookName)) { @@ -347,7 +348,7 @@ private void addEmbeddingToHookMap(byte[] data, String mimeType, String name) { } private Map createEmbeddingMap(byte[] data, String mimeType, String name) { - Map embedMap = new HashMap(); + Map embedMap = new HashMap<>(); embedMap.put("mime_type", mimeType); embedMap.put("data", Base64.encodeBytes(data)); if (name != null) { @@ -357,13 +358,13 @@ private Map createEmbeddingMap(byte[] data, String mimeType, Str } private Map createMatchMap(TestStep step, Result result) { - Map matchMap = new HashMap(); + Map matchMap = new HashMap<>(); if(step instanceof PickleStepTestStep) { PickleStepTestStep testStep = (PickleStepTestStep) step; if (!testStep.getDefinitionArgument().isEmpty()) { - List> argumentList = new ArrayList>(); - for (cucumber.api.Argument argument : testStep.getDefinitionArgument()) { - Map argumentMap = new HashMap(); + List> argumentList = new ArrayList<>(); + for (io.cucumber.core.event.Argument argument : testStep.getDefinitionArgument()) { + Map argumentMap = new HashMap<>(); if (argument.getValue() != null) { argumentMap.put("val", argument.getValue()); argumentMap.put("offset", argument.getStart()); @@ -373,28 +374,34 @@ private Map createMatchMap(TestStep step, Result result) { matchMap.put("arguments", argumentList); } } - if (!result.is(Result.Type.UNDEFINED)) { + if (!result.getStatus().is(Status.UNDEFINED)) { matchMap.put("location", step.getCodeLocation()); } return matchMap; } private Map createResultMap(Result result) { - Map resultMap = new HashMap(); - resultMap.put("status", result.getStatus().lowerCaseName()); - if (result.getErrorMessage() != null) { - resultMap.put("error_message", result.getErrorMessage()); + Map resultMap = new HashMap<>(); + resultMap.put("status", result.getStatus().name().toLowerCase(ROOT)); + if (result.getError() != null) { + resultMap.put("error_message", printStackTrace(result.getError())); } - if (result.getDuration() != 0) { - resultMap.put("duration", result.getDuration()); + if (!result.getDuration().isZero()) { + resultMap.put("duration", result.getDuration().toNanos()); } return resultMap; } - private String getDateTimeFromTimeStamp(long timeStampMillis) { - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); - sdf.setTimeZone(TimeZone.getTimeZone("UTC")); - - return sdf.format(new Date(timeStampMillis)); + private String getDateTimeFromTimeStamp(Instant instant) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX") + .withZone(ZoneOffset.UTC); + return formatter.format(instant); + } + + private static String printStackTrace(Throwable error) { + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + error.printStackTrace(printWriter); + return stringWriter.toString(); } } diff --git a/core/src/main/java/cucumber/runtime/formatter/JUnitFormatter.java b/core/src/main/java/io/cucumber/core/plugin/JUnitFormatter.java similarity index 78% rename from core/src/main/java/cucumber/runtime/formatter/JUnitFormatter.java rename to core/src/main/java/io/cucumber/core/plugin/JUnitFormatter.java index da12e7c55b..cae28ce10d 100644 --- a/core/src/main/java/cucumber/runtime/formatter/JUnitFormatter.java +++ b/core/src/main/java/io/cucumber/core/plugin/JUnitFormatter.java @@ -1,18 +1,17 @@ -package cucumber.runtime.formatter; - -import cucumber.api.PickleStepTestStep; -import cucumber.api.Result; -import cucumber.api.event.EventHandler; -import cucumber.api.event.EventListener; -import cucumber.api.event.EventPublisher; -import cucumber.api.event.TestCaseFinished; -import cucumber.api.event.TestCaseStarted; -import cucumber.api.event.TestRunFinished; -import cucumber.api.event.TestSourceRead; -import cucumber.api.event.TestStepFinished; -import cucumber.api.formatter.StrictAware; -import cucumber.runtime.CucumberException; -import cucumber.runtime.Utils; +package io.cucumber.core.plugin; + +import io.cucumber.core.event.PickleStepTestStep; +import io.cucumber.core.event.EventHandler; +import io.cucumber.core.event.EventPublisher; +import io.cucumber.core.event.Result; +import io.cucumber.core.event.Status; +import io.cucumber.core.event.TestCaseFinished; +import io.cucumber.core.event.TestCaseStarted; +import io.cucumber.core.event.TestRunFinished; +import io.cucumber.core.event.TestSourceRead; +import io.cucumber.core.event.TestStepFinished; +import io.cucumber.core.exception.CucumberException; + import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; @@ -26,6 +25,10 @@ import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; + +import static java.util.Locale.ROOT; +import static java.util.concurrent.TimeUnit.SECONDS; + import java.io.Closeable; import java.io.IOException; import java.io.PrintWriter; @@ -38,8 +41,9 @@ import java.util.List; import java.util.Locale; -final class JUnitFormatter implements EventListener, StrictAware { +public final class JUnitFormatter implements EventListener, StrictAware { + private static final long NANOS_PER_SECONDS = SECONDS.toNanos(1L); private final Writer writer; private final Document document; private final Element rootElement; @@ -94,6 +98,10 @@ public JUnitFormatter(URL writer) throws IOException { } } + private static String getUniqueTestNameForScenarioExample(String testCaseName, int exampleNumber) { + return testCaseName + (testCaseName.contains(" ") ? " " : "_") + exampleNumber; + } + @Override public void setEventPublisher(EventPublisher publisher) { publisher.registerHandlerFor(TestSourceRead.class, testSourceReadHandler); @@ -109,16 +117,16 @@ public void setStrict(boolean strict) { } private void handleTestSourceRead(TestSourceRead event) { - testSources.addTestSourceReadEvent(event.uri, event); + testSources.addTestSourceReadEvent(event.getUri(), event); } private void handleTestCaseStarted(TestCaseStarted event) { - if (currentFeatureFile == null || !currentFeatureFile.equals(event.testCase.getUri())) { - currentFeatureFile = event.testCase.getUri(); + if (currentFeatureFile == null || !currentFeatureFile.equals(event.getTestCase().getUri())) { + currentFeatureFile = event.getTestCase().getUri(); previousTestCaseName = ""; exampleNumber = 1; } - testCase = new TestCase(event.testCase); + testCase = new TestCase(event.getTestCase()); root = testCase.createElement(document); testCase.writeElement(root); rootElement.appendChild(root); @@ -127,17 +135,17 @@ private void handleTestCaseStarted(TestCaseStarted event) { } private void handleTestStepFinished(TestStepFinished event) { - if (event.testStep instanceof PickleStepTestStep) { - testCase.steps.add((PickleStepTestStep) event.testStep); - testCase.results.add(event.result); + if (event.getTestStep() instanceof PickleStepTestStep) { + testCase.steps.add((PickleStepTestStep) event.getTestStep()); + testCase.results.add(event.getResult()); } } private void handleTestCaseFinished(TestCaseFinished event) { if (testCase.steps.isEmpty()) { - testCase.handleEmptyTestCase(document, root, event.result); + testCase.handleEmptyTestCase(document, root, event.getResult()); } else { - testCase.addTestCaseElement(document, root, event.result); + testCase.addTestCaseElement(document, root, event.getResult()); } } @@ -198,9 +206,9 @@ final class TestCase { private final List steps = new ArrayList<>(); private final List results = new ArrayList<>(); - private final cucumber.api.TestCase testCase; + private final io.cucumber.core.event.TestCase testCase; - TestCase(cucumber.api.TestCase testCase) { + TestCase(io.cucumber.core.event.TestCase testCase) { this.testCase = testCase; } @@ -213,10 +221,10 @@ void writeElement(Element tc) { tc.setAttribute("name", calculateElementName(testCase)); } - private String calculateElementName(cucumber.api.TestCase testCase) { + private String calculateElementName(io.cucumber.core.event.TestCase testCase) { String testCaseName = testCase.getName(); if (testCaseName.equals(previousTestCaseName)) { - return Utils.getUniqueTestNameForScenarioExample(testCaseName, ++exampleNumber); + return getUniqueTestNameForScenarioExample(testCaseName, ++exampleNumber); } else { previousTestCaseName = testCase.getName(); exampleNumber = 1; @@ -230,18 +238,19 @@ void addTestCaseElement(Document doc, Element tc, Result result) { StringBuilder sb = new StringBuilder(); addStepAndResultListing(sb); Element child; - if (result.is(Result.Type.FAILED) || result.is(Result.Type.AMBIGUOUS)) { + Status status = result.getStatus(); + if (status.is(Status.FAILED) || status.is(Status.AMBIGUOUS)) { addStackTrace(sb, result); - child = createElementWithMessage(doc, sb, "failure", result.getErrorMessage()); - } else if (result.is(Result.Type.PENDING) || result.is(Result.Type.UNDEFINED)) { + child = createElementWithMessage(doc, sb, "failure", printStackTrace(result.getError())); + } else if (status.is(Status.PENDING) || status.is(Status.UNDEFINED)) { if (strict) { child = createElementWithMessage(doc, sb, "failure", "The scenario has pending or undefined step(s)"); } else { child = createElement(doc, sb, "skipped"); } - } else if (result.is(Result.Type.SKIPPED) && result.getError() != null) { + } else if (status.is(Status.SKIPPED) && result.getError() != null) { addStackTrace(sb, result); - child = createElementWithMessage(doc, sb, "skipped", result.getErrorMessage()); + child = createElementWithMessage(doc, sb, "skipped", printStackTrace(result.getError())); } else { child = createElement(doc, sb, "system-out"); } @@ -261,7 +270,7 @@ void handleEmptyTestCase(Document doc, Element tc, Result result) { private String calculateTotalDurationString(Result result) { DecimalFormat numberFormat = (DecimalFormat) NumberFormat.getNumberInstance(Locale.US); numberFormat.applyPattern("0.######"); - return numberFormat.format(((double) result.getDuration()) / 1000000000); + return numberFormat.format(((double) result.getDuration().toNanos() / NANOS_PER_SECONDS)); } private void addStepAndResultListing(StringBuilder sb) { @@ -269,7 +278,7 @@ private void addStepAndResultListing(StringBuilder sb) { int length = sb.length(); String resultStatus = "not executed"; if (i < results.size()) { - resultStatus = results.get(i).getStatus().lowerCaseName(); + resultStatus = results.get(i).getStatus().name().toLowerCase(ROOT); } sb.append(getKeywordFromSource(steps.get(i).getStepLine())); sb.append(steps.get(i).getStepText()); @@ -287,9 +296,13 @@ private String getKeywordFromSource(int stepLine) { private void addStackTrace(StringBuilder sb, Result failed) { sb.append("\nStackTrace:\n"); - StringWriter sw = new StringWriter(); - failed.getError().printStackTrace(new PrintWriter(sw)); - sb.append(sw.toString()); + sb.append(printStackTrace(failed.getError())); + } + + private String printStackTrace(Throwable error) { + StringWriter stringWriter = new StringWriter(); + error.printStackTrace(new PrintWriter(stringWriter)); + return stringWriter.toString(); } private Element createElementWithMessage(Document doc, StringBuilder sb, String elementType, String message) { @@ -303,7 +316,8 @@ private Element createElement(Document doc, StringBuilder sb, String elementType // the createCDATASection method seems to convert "\n" to "\r\n" on Windows, in case // data originally contains "\r\n" line separators the result becomes "\r\r\n", which // are displayed as double line breaks. - child.appendChild(doc.createCDATASection(sb.toString().replace(System.lineSeparator(), "\n"))); + String normalizedLineEndings = sb.toString().replace(System.lineSeparator(), "\n"); + child.appendChild(doc.createCDATASection(normalizedLineEndings)); return child; } diff --git a/core/src/main/java/cucumber/runtime/formatter/MonochromeFormats.java b/core/src/main/java/io/cucumber/core/plugin/MonochromeFormats.java similarity index 89% rename from core/src/main/java/cucumber/runtime/formatter/MonochromeFormats.java rename to core/src/main/java/io/cucumber/core/plugin/MonochromeFormats.java index 716f5133ec..2598d540be 100644 --- a/core/src/main/java/cucumber/runtime/formatter/MonochromeFormats.java +++ b/core/src/main/java/io/cucumber/core/plugin/MonochromeFormats.java @@ -1,4 +1,4 @@ -package cucumber.runtime.formatter; +package io.cucumber.core.plugin; final class MonochromeFormats implements Formats { private static final Format FORMAT = new Format() { diff --git a/core/src/main/java/cucumber/api/formatter/NiceAppendable.java b/core/src/main/java/io/cucumber/core/plugin/NiceAppendable.java similarity index 95% rename from core/src/main/java/cucumber/api/formatter/NiceAppendable.java rename to core/src/main/java/io/cucumber/core/plugin/NiceAppendable.java index 1032663bb9..132222657a 100644 --- a/core/src/main/java/cucumber/api/formatter/NiceAppendable.java +++ b/core/src/main/java/io/cucumber/core/plugin/NiceAppendable.java @@ -1,4 +1,4 @@ -package cucumber.api.formatter; +package io.cucumber.core.plugin; import java.io.Closeable; import java.io.Flushable; @@ -7,7 +7,7 @@ /** * A nice appendable that doesn't throw checked exceptions */ -public class NiceAppendable implements Appendable { +final class NiceAppendable implements Appendable { private static final CharSequence NL = "\n"; private final Appendable out; diff --git a/core/src/main/java/io/cucumber/core/plugin/NullSummaryPrinter.java b/core/src/main/java/io/cucumber/core/plugin/NullSummaryPrinter.java new file mode 100644 index 0000000000..3c0bf85d0d --- /dev/null +++ b/core/src/main/java/io/cucumber/core/plugin/NullSummaryPrinter.java @@ -0,0 +1,10 @@ +package io.cucumber.core.plugin; + +public final class NullSummaryPrinter implements SummaryPrinter { + + @SuppressWarnings("WeakerAccess") // Used by PluginFactory + public NullSummaryPrinter(){ + + } + +} diff --git a/core/src/main/java/io/cucumber/core/plugin/Options.java b/core/src/main/java/io/cucumber/core/plugin/Options.java new file mode 100644 index 0000000000..583495e7e1 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/plugin/Options.java @@ -0,0 +1,18 @@ +package io.cucumber.core.plugin; + +public interface Options { + Iterable plugins(); + + boolean isMonochrome(); + + boolean isStrict(); + + interface Plugin { + + Class pluginClass(); + + String argument(); + + String pluginString(); + } +} diff --git a/core/src/main/java/cucumber/api/Plugin.java b/core/src/main/java/io/cucumber/core/plugin/Plugin.java similarity index 66% rename from core/src/main/java/cucumber/api/Plugin.java rename to core/src/main/java/io/cucumber/core/plugin/Plugin.java index e566927a1f..82d0f3835c 100644 --- a/core/src/main/java/cucumber/api/Plugin.java +++ b/core/src/main/java/io/cucumber/core/plugin/Plugin.java @@ -1,4 +1,6 @@ -package cucumber.api; +package io.cucumber.core.plugin; + +import org.apiguardian.api.API; import java.io.File; import java.net.URI; @@ -10,7 +12,7 @@ * A plugin can be added to the runtime to listen in on step definition, summary printing and test * execution. *

- * Plugins are added to the runtime from the command line or @{@link CucumberOptions} and may be provided with a + * Plugins are added to the runtime from the command line or @{@link io.cucumber.core.options.CucumberOptionsAnnotationParser.CucumberOptions} and may be provided with a * parameter. To accept this parameter the plugin must have a public constructor that accepts one of the following * arguments: *

    @@ -25,13 +27,13 @@ *

    * Plugins may also implement one of these interfaces: *

      - *
    • {@link cucumber.api.formatter.ColorAware}
    • - *
    • {@link cucumber.api.formatter.StrictAware}
    • - *
    • {@link cucumber.api.event.EventListener}
    • - *
    • {@link cucumber.api.event.ConcurrentEventListener}
    • - *
    • {@link cucumber.api.StepDefinitionReporter}
    • - *
    • {@link cucumber.api.SummaryPrinter}
    • + *
    • {@link ColorAware}
    • + *
    • {@link StrictAware}
    • + *
    • {@link EventListener}
    • + *
    • {@link ConcurrentEventListener}
    • + *
    • {@link SummaryPrinter}
    • *
    */ +@API(status = API.Status.STABLE) public interface Plugin { } diff --git a/core/src/main/java/cucumber/runtime/formatter/PluginFactory.java b/core/src/main/java/io/cucumber/core/plugin/PluginFactory.java similarity index 52% rename from core/src/main/java/cucumber/runtime/formatter/PluginFactory.java rename to core/src/main/java/io/cucumber/core/plugin/PluginFactory.java index bfafe3c2c9..1690f9fedb 100644 --- a/core/src/main/java/cucumber/runtime/formatter/PluginFactory.java +++ b/core/src/main/java/io/cucumber/core/plugin/PluginFactory.java @@ -1,25 +1,17 @@ -package cucumber.runtime.formatter; +package io.cucumber.core.plugin; -import cucumber.api.Plugin; -import cucumber.api.StepDefinitionReporter; -import cucumber.api.SummaryPrinter; -import cucumber.api.event.ConcurrentEventListener; -import cucumber.api.event.EventListener; -import cucumber.runtime.CucumberException; +import io.cucumber.core.exception.CucumberException; import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; +import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; -import java.util.HashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import static cucumber.runtime.Utils.toURL; import static java.util.Arrays.asList; /** @@ -27,29 +19,12 @@ *

    * The String is of the form name[:output] where name is either a fully qualified class name or one of the built-in * short names. The output is optional for some plugins (and mandatory for some). - *

* * @see Plugin for specific requirements */ public final class PluginFactory { private final Class[] CTOR_PARAMETERS = new Class[]{String.class, Appendable.class, URI.class, URL.class, File.class}; - private static final HashMap> PLUGIN_CLASSES = new HashMap>() {{ - put("junit", JUnitFormatter.class); - put("testng", TestNGFormatter.class); - put("html", HTMLFormatter.class); - put("pretty", PrettyFormatter.class); - put("progress", ProgressFormatter.class); - put("json", JSONFormatter.class); - put("usage", UsageFormatter.class); - put("rerun", RerunFormatter.class); - put("default_summary", DefaultSummaryPrinter.class); - put("summary", DefaultSummaryPrinter.class); - put("null_summary", NullSummaryPrinter.class); - put("unused", UnusedStepsSummaryPrinter.class); - put("timeline", TimelineFormatter.class); - }}; - private static final Pattern PLUGIN_WITH_ARGUMENT_PATTERN = Pattern.compile("([^:]+):(.*)"); private String defaultOutFormatter = null; private Appendable defaultOut = new PrintStream(System.out) { @@ -59,23 +34,25 @@ public void close() { } }; - public Plugin create(String pluginString) { - Matcher pluginWithArgument = PLUGIN_WITH_ARGUMENT_PATTERN.matcher(pluginString); - String pluginName; - String argument; - if (pluginWithArgument.matches()) { - pluginName = pluginWithArgument.group(1); - argument = pluginWithArgument.group(2); - } else { - pluginName = pluginString; - argument = null; + static URL toURL(String pathOrUrl) { + try { + if (!pathOrUrl.endsWith("/")) { + pathOrUrl = pathOrUrl + "/"; + } + if (pathOrUrl.matches("^(file|http|https):.*")) { + return new URL(pathOrUrl); + } else { + return new URL("file:" + pathOrUrl); + } + } catch (MalformedURLException e) { + throw new CucumberException("Bad URL:" + pathOrUrl, e); } - Class pluginClass = pluginClass(pluginName); + } + + Plugin create(Options.Plugin plugin) { try { - return instantiate(pluginString, pluginClass, argument); - } catch (IOException e) { - throw new CucumberException(e); - } catch (URISyntaxException e) { + return instantiate(plugin.pluginString(), plugin.pluginClass(), plugin.argument()); + } catch (IOException | URISyntaxException e) { throw new CucumberException(e); } } @@ -101,9 +78,7 @@ private T instantiate(String pluginString, Class pluginCla private T newInstance(Constructor constructor, Object... ctorArgs) { try { return constructor.newInstance(ctorArgs); - } catch (InstantiationException e) { - throw new CucumberException(e); - } catch (IllegalAccessException e) { + } catch (InstantiationException | IllegalAccessException e) { throw new CucumberException(e); } catch (InvocationTargetException e) { throw new CucumberException(e.getTargetException()); @@ -159,66 +134,19 @@ private Constructor findEmptyConstructor(Class pluginClass) { } } - private static Class pluginClass(String pluginName) { - Class pluginClass = PLUGIN_CLASSES.get(pluginName); - if (pluginClass == null) { - pluginClass = loadClass(pluginName); - } - return pluginClass; - } - - @SuppressWarnings("unchecked") - private static Class loadClass(String className) { - try { - Class aClass = Thread.currentThread().getContextClassLoader().loadClass(className); - - if (Plugin.class.isAssignableFrom(aClass)) { - return (Class) aClass; - } - throw new CucumberException("Couldn't load plugin class: " + className + ". It does not implement " + Plugin.class.getName()); - } catch (ClassNotFoundException e) { - throw new CucumberException("Couldn't load plugin class: " + className, e); - } - } - private Appendable defaultOutOrFailIfAlreadyUsed(String formatterString) { try { if (defaultOut != null) { defaultOutFormatter = formatterString; return defaultOut; } else { - throw new CucumberException("Only one formatter can use STDOUT, now both " + + throw new CucumberException("Only one plugin can use STDOUT, now both " + defaultOutFormatter + " and " + formatterString + " use it. " + - "If you use more than one formatter you must specify output path with PLUGIN:PATH_OR_URL"); + "If you use more than one plugin you must specify output path with PLUGIN:PATH_OR_URL"); } } finally { defaultOut = null; } } - public static boolean isFormatterName(String name) { - Class pluginClass = getPluginClass(name); - return EventListener.class.isAssignableFrom(pluginClass) || ConcurrentEventListener.class.isAssignableFrom(pluginClass); - } - - public static boolean isStepDefinitionReporterName(String name) { - Class pluginClass = getPluginClass(name); - return StepDefinitionReporter.class.isAssignableFrom(pluginClass); - } - - public static boolean isSummaryPrinterName(String name) { - Class pluginClass = getPluginClass(name); - return SummaryPrinter.class.isAssignableFrom(pluginClass); - } - - private static Class getPluginClass(String name) { - Matcher pluginWithFile = PLUGIN_WITH_ARGUMENT_PATTERN.matcher(name); - String pluginName; - if (pluginWithFile.matches()) { - pluginName = pluginWithFile.group(1); - } else { - pluginName = name; - } - return pluginClass(pluginName); - } } diff --git a/core/src/main/java/cucumber/runtime/formatter/Plugins.java b/core/src/main/java/io/cucumber/core/plugin/Plugins.java similarity index 58% rename from core/src/main/java/cucumber/runtime/formatter/Plugins.java rename to core/src/main/java/io/cucumber/core/plugin/Plugins.java index aeb4f41495..8fbe88ab40 100644 --- a/core/src/main/java/cucumber/runtime/formatter/Plugins.java +++ b/core/src/main/java/io/cucumber/core/plugin/Plugins.java @@ -1,35 +1,21 @@ -package cucumber.runtime.formatter; - -import cucumber.api.Plugin; -import cucumber.api.StepDefinitionReporter; -import cucumber.api.event.ConcurrentEventListener; -import cucumber.api.event.Event; -import cucumber.api.event.EventHandler; -import cucumber.api.event.EventListener; -import cucumber.api.event.EventPublisher; -import cucumber.api.formatter.ColorAware; -import cucumber.api.formatter.StrictAware; -import cucumber.runner.CanonicalOrderEventPublisher; -import io.cucumber.core.options.PluginOptions; -import cucumber.runtime.Utils; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; +package io.cucumber.core.plugin; + +import io.cucumber.core.event.Event; +import io.cucumber.core.event.EventHandler; +import io.cucumber.core.event.EventPublisher; + import java.util.ArrayList; import java.util.List; public final class Plugins { private final List plugins; - private final ClassLoader classLoader; private boolean pluginNamesInstantiated; private final PluginFactory pluginFactory; private EventPublisher orderedEventPublisher; - private final PluginOptions pluginOptions; + private final Options pluginOptions; - public Plugins(ClassLoader classLoader, PluginFactory pluginFactory, PluginOptions pluginOptions) { - this.classLoader = classLoader; + public Plugins(PluginFactory pluginFactory, Options pluginOptions) { this.pluginFactory = pluginFactory; this.pluginOptions = pluginOptions; this.plugins = createPlugins(); @@ -59,8 +45,8 @@ public void receive(Event event) { private List createPlugins() { List plugins = new ArrayList(); if (!pluginNamesInstantiated) { - for (String pluginName : pluginOptions.getPluginNames()) { - Plugin plugin = pluginFactory.create(pluginName); + for (Options.Plugin pluginOption : pluginOptions.plugins()) { + Plugin plugin = pluginFactory.create(pluginOption); addPlugin(plugins, plugin); } pluginNamesInstantiated = true; @@ -72,10 +58,6 @@ public List getPlugins() { return plugins; } - public StepDefinitionReporter stepDefinitionReporter() { - return pluginProxy(StepDefinitionReporter.class); - } - public void addPlugin(Plugin plugin) { addPlugin(plugins, plugin); } @@ -121,34 +103,4 @@ public void setSerialEventBusOnEventListenerPlugins(EventPublisher eventPublishe } } - /** - * Creates a dynamic proxy that multiplexes method invocations to all plugins of the same type. - * - * @param type proxy type - * @param generic proxy type - * @return a proxy - */ - private T pluginProxy(final Class type) { - Object proxy = Proxy.newProxyInstance(classLoader, new Class[]{type}, new InvocationHandler() { - @Override - public Object invoke(Object target, Method method, Object[] args) throws Throwable { - for (Object plugin : getPlugins()) { - if (type.isInstance(plugin)) { - try { - Utils.invoke(plugin, method, 0, args); - } catch (Throwable t) { - if (!method.getName().equals("startOfScenarioLifeCycle") && !method.getName().equals("endOfScenarioLifeCycle")) { - // IntelliJ has its own formatter which doesn't yet implement these methods. - throw t; - } - } - } - } - return null; - } - }); - return type.cast(proxy); - } - - } diff --git a/core/src/main/java/cucumber/runtime/formatter/PrettyFormatter.java b/core/src/main/java/io/cucumber/core/plugin/PrettyFormatter.java similarity index 83% rename from core/src/main/java/cucumber/runtime/formatter/PrettyFormatter.java rename to core/src/main/java/io/cucumber/core/plugin/PrettyFormatter.java index 61c8ca2247..0767375504 100644 --- a/core/src/main/java/cucumber/runtime/formatter/PrettyFormatter.java +++ b/core/src/main/java/io/cucumber/core/plugin/PrettyFormatter.java @@ -1,24 +1,18 @@ -package cucumber.runtime.formatter; - -import cucumber.api.Argument; -import cucumber.api.PickleStepTestStep; -import cucumber.api.Result; -import cucumber.api.TestCase; -import cucumber.api.TestStep; -import cucumber.api.event.ConcurrentEventListener; -import cucumber.api.event.EventHandler; -import cucumber.api.event.EventListener; -import cucumber.api.event.EventPublisher; -import cucumber.api.event.TestCaseStarted; -import cucumber.api.event.TestRunFinished; -import cucumber.api.event.TestSourceRead; -import cucumber.api.event.TestStepFinished; -import cucumber.api.event.TestStepStarted; -import cucumber.api.event.WriteEvent; -import cucumber.api.formatter.ColorAware; -import cucumber.api.formatter.NiceAppendable; -import cucumber.util.FixJava; -import cucumber.util.Mapper; +package io.cucumber.core.plugin; + +import io.cucumber.core.event.Argument; +import io.cucumber.core.event.PickleStepTestStep; +import io.cucumber.core.event.Result; +import io.cucumber.core.event.TestCase; +import io.cucumber.core.event.TestStep; +import io.cucumber.core.event.EventHandler; +import io.cucumber.core.event.EventPublisher; +import io.cucumber.core.event.TestCaseStarted; +import io.cucumber.core.event.TestRunFinished; +import io.cucumber.core.event.TestSourceRead; +import io.cucumber.core.event.TestStepFinished; +import io.cucumber.core.event.TestStepStarted; +import io.cucumber.core.event.WriteEvent; import gherkin.ast.Background; import gherkin.ast.Examples; import gherkin.ast.Feature; @@ -26,12 +20,16 @@ import gherkin.ast.ScenarioOutline; import gherkin.ast.Step; import gherkin.ast.Tag; -import gherkin.pickles.PickleTag; +import java.io.PrintWriter; +import java.io.StringWriter; import java.net.URI; import java.util.List; -final class PrettyFormatter implements EventListener, ColorAware { +import static java.util.Locale.ROOT; +import static java.util.stream.Collectors.joining; + +public final class PrettyFormatter implements EventListener, ColorAware { private static final String SCENARIO_INDENT = " "; private static final String STEP_INDENT = " "; private static final String EXAMPLES_INDENT = " "; @@ -43,18 +41,6 @@ final class PrettyFormatter implements EventListener, ColorAware { private ScenarioOutline currentScenarioOutline; private Examples currentExamples; private int locationIndentation; - private Mapper tagNameMapper = new Mapper() { - @Override - public String map(Tag tag) { - return tag.getName(); - } - }; - private Mapper pickleTagNameMapper = new Mapper() { - @Override - public String map(PickleTag pickleTag) { - return pickleTag.getName(); - } - }; private EventHandler testSourceReadHandler = new EventHandler() { @Override @@ -119,23 +105,23 @@ public void setMonochrome(boolean monochrome) { } private void handleTestSourceRead(TestSourceRead event) { - testSources.addTestSourceReadEvent(event.uri, event); + testSources.addTestSourceReadEvent(event.getUri(), event); } private void handleTestCaseStarted(TestCaseStarted event) { handleStartOfFeature(event); handleScenarioOutline(event); - if (testSources.hasBackground(currentFeatureFile, event.testCase.getLine())) { - printBackground(event.testCase); - currentTestCase = event.testCase; + if (testSources.hasBackground(currentFeatureFile, event.getTestCase().getLine())) { + printBackground(event.getTestCase()); + currentTestCase = event.getTestCase(); } else { - printScenarioDefinition(event.testCase); + printScenarioDefinition(event.getTestCase()); } } private void handleTestStepStarted(TestStepStarted event) { - if (event.testStep instanceof PickleStepTestStep) { - if (isFirstStepAfterBackground((PickleStepTestStep) event.testStep)) { + if (event.getTestStep() instanceof PickleStepTestStep) { + if (isFirstStepAfterBackground((PickleStepTestStep) event.getTestStep())) { printScenarioDefinition(currentTestCase); currentTestCase = null; } @@ -143,14 +129,14 @@ private void handleTestStepStarted(TestStepStarted event) { } private void handleTestStepFinished(TestStepFinished event) { - if (event.testStep instanceof PickleStepTestStep) { - printStep((PickleStepTestStep) event.testStep, event.result); + if (event.getTestStep() instanceof PickleStepTestStep) { + printStep((PickleStepTestStep) event.getTestStep(), event.getResult()); } - printError(event.result); + printError(event.getResult()); } private void handleWrite(WriteEvent event) { - out.println(event.text); + out.println(event.getText()); } private void finishReport() { @@ -158,17 +144,17 @@ private void finishReport() { } private void handleStartOfFeature(TestCaseStarted event) { - if (currentFeatureFile == null || !currentFeatureFile.equals(event.testCase.getUri())) { + if (currentFeatureFile == null || !currentFeatureFile.equals(event.getTestCase().getUri())) { if (currentFeatureFile != null) { out.println(); } - currentFeatureFile = event.testCase.getUri(); + currentFeatureFile = event.getTestCase().getUri(); printFeature(currentFeatureFile); } } private void handleScenarioOutline(TestCaseStarted event) { - TestSourcesModel.AstNode astNode = testSources.getAstNode(currentFeatureFile, event.testCase.getLine()); + TestSourcesModel.AstNode astNode = testSources.getAstNode(currentFeatureFile, event.getTestCase().getLine()); if (TestSourcesModel.isScenarioOutlineScenario(astNode)) { ScenarioOutline scenarioOutline = (ScenarioOutline)TestSourcesModel.getScenarioDefinition(astNode); if (currentScenarioOutline == null || !currentScenarioOutline.equals(scenarioOutline)) { @@ -206,7 +192,8 @@ private void printStep(PickleStepTestStep testStep, Result result) { String keyword = getStepKeyword(testStep); String stepText = testStep.getStepText(); String locationPadding = createPaddingToLocation(STEP_INDENT, keyword + stepText); - String formattedStepText = formatStepText(keyword, stepText, formats.get(result.getStatus().lowerCaseName()), formats.get(result.getStatus().lowerCaseName() + "_arg"), testStep.getDefinitionArgument()); + String status = result.getStatus().name().toLowerCase(ROOT); + String formattedStepText = formatStepText(keyword, stepText, formats.get(status), formats.get(status + "_arg"), testStep.getDefinitionArgument()); out.println(STEP_INDENT + formattedStepText + locationPadding + getLocationText(testStep.getCodeLocation())); } @@ -233,7 +220,7 @@ String formatStepText(String keyword, String stepText, Format textFormat, Format } } if (beginIndex != stepText.length()) { - String text = stepText.substring(beginIndex, stepText.length()); + String text = stepText.substring(beginIndex); result.append(textFormat.text(text)); } return result.toString(); @@ -291,13 +278,13 @@ private void printTags(List tags) { } private void printTags(List tags, String indent) { if (!tags.isEmpty()) { - out.println(indent + FixJava.join(FixJava.map(tags, tagNameMapper), " ")); + out.println(indent + tags.stream().map(Tag::getName).collect(joining(" "))); } } - private void printPickleTags(List tags, String indent) { + private void printPickleTags(List tags, String indent) { if (!tags.isEmpty()) { - out.println(indent + FixJava.join(FixJava.map(tags, pickleTagNameMapper), " ")); + out.println(indent + String.join(" ", tags)); } } @@ -334,7 +321,8 @@ private void printScenarioDefinition(TestCase testCase) { private void printError(Result result) { if (result.getError() != null) { - out.println(" " + formats.get(result.getStatus().lowerCaseName()).text(result.getErrorMessage())); + String name = result.getStatus().name().toLowerCase(ROOT); + out.println(" " + formats.get(name).text(printStackTrace(result.getError()))); } } @@ -358,10 +346,17 @@ private void calculateLocationIndentation(String definitionText, List } private String createPaddingToLocation(String indent, String text) { - StringBuffer padding = new StringBuffer(); + StringBuilder padding = new StringBuilder(); for (int i = indent.length() + text.length(); i < locationIndentation; ++i) { padding.append(' '); } return padding.toString(); } + + private static String printStackTrace(Throwable error) { + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + error.printStackTrace(printWriter); + return stringWriter.toString(); + } } diff --git a/core/src/main/java/cucumber/runtime/formatter/ProgressFormatter.java b/core/src/main/java/io/cucumber/core/plugin/ProgressFormatter.java similarity index 50% rename from core/src/main/java/cucumber/runtime/formatter/ProgressFormatter.java rename to core/src/main/java/io/cucumber/core/plugin/ProgressFormatter.java index 99953501bb..2be839d27c 100644 --- a/core/src/main/java/cucumber/runtime/formatter/ProgressFormatter.java +++ b/core/src/main/java/io/cucumber/core/plugin/ProgressFormatter.java @@ -1,36 +1,32 @@ -package cucumber.runtime.formatter; +package io.cucumber.core.plugin; -import cucumber.api.PickleStepTestStep; -import cucumber.api.Result; -import cucumber.api.event.ConcurrentEventListener; -import cucumber.api.event.EventHandler; -import cucumber.api.event.EventPublisher; -import cucumber.api.event.TestRunFinished; -import cucumber.api.event.TestStepFinished; -import cucumber.api.event.WriteEvent; -import cucumber.api.formatter.AnsiEscapes; -import cucumber.api.formatter.ColorAware; -import cucumber.api.formatter.NiceAppendable; +import io.cucumber.core.event.PickleStepTestStep; +import io.cucumber.core.event.EventHandler; +import io.cucumber.core.event.EventPublisher; +import io.cucumber.core.event.Status; +import io.cucumber.core.event.TestRunFinished; +import io.cucumber.core.event.TestStepFinished; +import io.cucumber.core.event.WriteEvent; import java.util.HashMap; import java.util.Map; -final class ProgressFormatter implements ConcurrentEventListener, ColorAware { - private static final Map CHARS = new HashMap() {{ - put(Result.Type.PASSED, '.'); - put(Result.Type.UNDEFINED, 'U'); - put(Result.Type.PENDING, 'P'); - put(Result.Type.SKIPPED, '-'); - put(Result.Type.FAILED, 'F'); - put(Result.Type.AMBIGUOUS, 'A'); +public final class ProgressFormatter implements ConcurrentEventListener, ColorAware { + private static final Map CHARS = new HashMap() {{ + put(Status.PASSED, '.'); + put(Status.UNDEFINED, 'U'); + put(Status.PENDING, 'P'); + put(Status.SKIPPED, '-'); + put(Status.FAILED, 'F'); + put(Status.AMBIGUOUS, 'A'); }}; - private static final Map ANSI_ESCAPES = new HashMap() {{ - put(Result.Type.PASSED, AnsiEscapes.GREEN); - put(Result.Type.UNDEFINED, AnsiEscapes.YELLOW); - put(Result.Type.PENDING, AnsiEscapes.YELLOW); - put(Result.Type.SKIPPED, AnsiEscapes.CYAN); - put(Result.Type.FAILED, AnsiEscapes.RED); - put(Result.Type.AMBIGUOUS, AnsiEscapes.RED); + private static final Map ANSI_ESCAPES = new HashMap() {{ + put(Status.PASSED, AnsiEscapes.GREEN); + put(Status.UNDEFINED, AnsiEscapes.YELLOW); + put(Status.PENDING, AnsiEscapes.YELLOW); + put(Status.SKIPPED, AnsiEscapes.CYAN); + put(Status.FAILED, AnsiEscapes.RED); + put(Status.AMBIGUOUS, AnsiEscapes.RED); }}; private final NiceAppendable out; @@ -54,7 +50,6 @@ public void receive(TestRunFinished event) { } }; - @SuppressWarnings("WeakerAccess") // Used by PluginFactory public ProgressFormatter(Appendable appendable) { out = new NiceAppendable(appendable); } @@ -72,11 +67,11 @@ public void setEventPublisher(EventPublisher publisher) { } private void handleTestStepFinished(TestStepFinished event) { - if (event.testStep instanceof PickleStepTestStep || event.result.is(Result.Type.FAILED)) { + if (event.getTestStep() instanceof PickleStepTestStep || event.getResult().getStatus().is(Status.FAILED)) { if (!monochrome) { - ANSI_ESCAPES.get(event.result.getStatus()).appendTo(out); + ANSI_ESCAPES.get(event.getResult().getStatus()).appendTo(out); } - out.append(CHARS.get(event.result.getStatus())); + out.append(CHARS.get(event.getResult().getStatus())); if (!monochrome) { AnsiEscapes.RESET.appendTo(out); } @@ -84,7 +79,7 @@ private void handleTestStepFinished(TestStepFinished event) { } private void handleWrite(WriteEvent event) { - out.append(event.text); + out.append(event.getText()); } private void handleTestRunFinished() { diff --git a/core/src/main/java/cucumber/runtime/formatter/RerunFormatter.java b/core/src/main/java/io/cucumber/core/plugin/RerunFormatter.java similarity index 69% rename from core/src/main/java/cucumber/runtime/formatter/RerunFormatter.java rename to core/src/main/java/io/cucumber/core/plugin/RerunFormatter.java index 8af11aba4d..e6c51a9f8b 100644 --- a/core/src/main/java/cucumber/runtime/formatter/RerunFormatter.java +++ b/core/src/main/java/io/cucumber/core/plugin/RerunFormatter.java @@ -1,14 +1,11 @@ -package cucumber.runtime.formatter; +package io.cucumber.core.plugin; -import cucumber.api.TestCase; -import cucumber.api.event.EventHandler; -import cucumber.api.event.EventListener; -import cucumber.api.event.EventPublisher; -import cucumber.api.event.TestCaseFinished; -import cucumber.api.event.TestRunFinished; -import cucumber.api.formatter.NiceAppendable; -import cucumber.api.formatter.StrictAware; -import io.cucumber.core.model.FeatureWithLines; +import io.cucumber.core.event.TestCase; +import io.cucumber.core.event.EventHandler; +import io.cucumber.core.event.EventPublisher; +import io.cucumber.core.event.TestCaseFinished; +import io.cucumber.core.event.TestRunFinished; +import io.cucumber.core.feature.FeatureWithLines; import java.util.ArrayList; import java.util.Collection; @@ -20,7 +17,7 @@ * Formatter for reporting all failed test cases and print their locations * Failed means: results that make the exit code non-zero. */ -final class RerunFormatter implements EventListener, StrictAware { +public final class RerunFormatter implements EventListener, StrictAware { private final NiceAppendable out; private final Map> featureAndFailedLinesMapping = new HashMap<>(); private final EventHandler runFinishHandler = new EventHandler() { @@ -40,8 +37,8 @@ public void receive(TestRunFinished event) { @Override public void receive(TestCaseFinished event) { - if (!event.result.isOk(isStrict)) { - recordTestFailed(event.testCase); + if (!event.getResult().getStatus().isOk(isStrict)) { + recordTestFailed(event.getTestCase()); } } @@ -52,12 +49,7 @@ private void recordTestFailed(TestCase testCase) { } private Collection getFailedTestCaseLines(String uri) { - Collection failedTestCases = featureAndFailedLinesMapping.get(uri); - if (failedTestCases == null) { - failedTestCases = new ArrayList<>(); - featureAndFailedLinesMapping.put(uri, failedTestCases); - } - return failedTestCases; + return featureAndFailedLinesMapping.computeIfAbsent(uri, k -> new ArrayList<>()); } }; diff --git a/core/src/main/java/cucumber/runtime/formatter/Stats.java b/core/src/main/java/io/cucumber/core/plugin/Stats.java similarity index 66% rename from core/src/main/java/cucumber/runtime/formatter/Stats.java rename to core/src/main/java/io/cucumber/core/plugin/Stats.java index 103e532c00..510f109ce6 100755 --- a/core/src/main/java/cucumber/runtime/formatter/Stats.java +++ b/core/src/main/java/io/cucumber/core/plugin/Stats.java @@ -1,33 +1,34 @@ -package cucumber.runtime.formatter; - -import cucumber.api.PickleStepTestStep; -import cucumber.api.Result; -import cucumber.api.event.EventHandler; -import cucumber.api.event.EventListener; -import cucumber.api.event.EventPublisher; -import cucumber.api.event.TestCaseFinished; -import cucumber.api.event.TestRunFinished; -import cucumber.api.event.TestRunStarted; -import cucumber.api.event.TestStepFinished; -import cucumber.api.formatter.ColorAware; -import cucumber.api.formatter.StrictAware; +package io.cucumber.core.plugin; + +import io.cucumber.core.event.Result; +import io.cucumber.core.event.PickleStepTestStep; +import io.cucumber.core.event.EventHandler; +import io.cucumber.core.event.EventPublisher; +import io.cucumber.core.event.Status; +import io.cucumber.core.event.TestCaseFinished; +import io.cucumber.core.event.TestRunFinished; +import io.cucumber.core.event.TestRunStarted; +import io.cucumber.core.event.TestStepFinished; import java.io.PrintStream; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; +import java.time.Duration; +import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import static java.util.Locale.ROOT; import static java.util.concurrent.TimeUnit.SECONDS; -public class Stats implements EventListener, ColorAware, StrictAware { - static final long ONE_SECOND = SECONDS.toNanos(1); - static final long ONE_MINUTE = 60 * ONE_SECOND; +class Stats implements EventListener, ColorAware, StrictAware { + private static final long ONE_SECOND = SECONDS.toNanos(1); + private static final long ONE_MINUTE = 60 * ONE_SECOND; private SubCounts scenarioSubCounts = new SubCounts(); private SubCounts stepSubCounts = new SubCounts(); - private long startTime = 0; - private long totalDuration = 0; + private Instant startTime = Instant.EPOCH; + private Duration totalDuration = Duration.ZERO; private Formats formats = new AnsiFormats(); private Locale locale; private final List failedScenarios = new ArrayList(); @@ -38,17 +39,17 @@ public class Stats implements EventListener, ColorAware, StrictAware { private final EventHandler testRunStartedHandler = new EventHandler() { @Override public void receive(TestRunStarted event) { - setStartTime(event.getTimeStamp()); + setStartTime(event.getInstant()); } }; private final EventHandler stepFinishedHandler = new EventHandler() { @Override public void receive(TestStepFinished event) { - Result result = event.result; + Result result = event.getResult(); if (result.getError() != null) { addError(result.getError()); } - if (event.testStep instanceof PickleStepTestStep) { + if (event.getTestStep() instanceof PickleStepTestStep) { addStep(result.getStatus()); } } @@ -56,18 +57,18 @@ public void receive(TestStepFinished event) { private final EventHandler testCaseFinishedHandler = new EventHandler() { @Override public void receive(TestCaseFinished event) { - addScenario(event.result.getStatus(), event.testCase.getScenarioDesignation()); + addScenario(event.getResult().getStatus(), event.getTestCase().getScenarioDesignation()); } }; private final EventHandler testRunFinishedHandler = new EventHandler() { @Override public void receive(TestRunFinished event) { - setFinishTime(event.getTimeStamp()); + setFinishTime(event.getInstant()); } }; private boolean strict; - public Stats() { + Stats() { this(Locale.getDefault()); } @@ -129,45 +130,45 @@ private void printScenarioCounts(PrintStream out) { private void printSubCounts(PrintStream out, SubCounts subCounts) { boolean addComma = false; - addComma = printSubCount(out, subCounts.failed, Result.Type.FAILED, addComma); - addComma = printSubCount(out, subCounts.ambiguous, Result.Type.AMBIGUOUS, addComma); - addComma = printSubCount(out, subCounts.skipped, Result.Type.SKIPPED, addComma); - addComma = printSubCount(out, subCounts.pending, Result.Type.PENDING, addComma); - addComma = printSubCount(out, subCounts.undefined, Result.Type.UNDEFINED, addComma); - addComma = printSubCount(out, subCounts.passed, Result.Type.PASSED, addComma); + addComma = printSubCount(out, subCounts.failed, Status.FAILED, addComma); + addComma = printSubCount(out, subCounts.ambiguous, Status.AMBIGUOUS, addComma); + addComma = printSubCount(out, subCounts.skipped, Status.SKIPPED, addComma); + addComma = printSubCount(out, subCounts.pending, Status.PENDING, addComma); + addComma = printSubCount(out, subCounts.undefined, Status.UNDEFINED, addComma); + addComma = printSubCount(out, subCounts.passed, Status.PASSED, addComma); } - private boolean printSubCount(PrintStream out, int count, Result.Type type, boolean addComma) { + private boolean printSubCount(PrintStream out, int count, Status type, boolean addComma) { if (count != 0) { if (addComma) { out.print(", "); } - Format format = formats.get(type.lowerCaseName()); - out.print(format.text(count + " " + type.lowerCaseName())); + Format format = formats.get(type.name().toLowerCase(ROOT)); + out.print(format.text(count + " " + type.name().toLowerCase(ROOT))); addComma = true; } return addComma; } private void printDuration(PrintStream out) { - out.print(String.format("%dm", (totalDuration / ONE_MINUTE))); + out.print(String.format("%dm", (totalDuration.toNanos() / ONE_MINUTE))); DecimalFormat format = new DecimalFormat("0.000", new DecimalFormatSymbols(locale)); - out.println(format.format(((double) (totalDuration % ONE_MINUTE)) / ONE_SECOND) + "s"); + out.println(format.format(((double) (totalDuration.toNanos() % ONE_MINUTE) / ONE_SECOND)) + "s"); } private void printNonZeroResultScenarios(PrintStream out) { - printScenarios(out, failedScenarios, Result.Type.FAILED); - printScenarios(out, ambiguousScenarios, Result.Type.AMBIGUOUS); + printScenarios(out, failedScenarios, Status.FAILED); + printScenarios(out, ambiguousScenarios, Status.AMBIGUOUS); if (strict) { - printScenarios(out, pendingScenarios, Result.Type.PENDING); - printScenarios(out, undefinedScenarios, Result.Type.UNDEFINED); + printScenarios(out, pendingScenarios, Status.PENDING); + printScenarios(out, undefinedScenarios, Status.UNDEFINED); } } - private void printScenarios(PrintStream out, List scenarios, Result.Type type) { - Format format = formats.get(type.lowerCaseName()); + private void printScenarios(PrintStream out, List scenarios, Status type) { + Format format = formats.get(type.name().toLowerCase(ROOT)); if (!scenarios.isEmpty()) { - out.println(format.text(type.firstLetterCapitalizedName() + " scenarios:")); + out.println(format.text(firstLetterCapitalizedName(type) + " scenarios:")); } for (String scenario : scenarios) { String[] parts = scenario.split("#"); @@ -181,7 +182,12 @@ private void printScenarios(PrintStream out, List scenarios, Result.Type } } - void addStep(Result.Type resultStatus) { + public String firstLetterCapitalizedName(Status status) { + String name = status.name(); + return name.substring(0, 1) + name.substring(1).toLowerCase(ROOT); + } + + void addStep(Status resultStatus) { addResultToSubCount(stepSubCounts, resultStatus); } @@ -189,15 +195,15 @@ private void addError(Throwable error) { errors.add(error); } - void setStartTime(Long startTime) { + void setStartTime(Instant startTime) { this.startTime = startTime; } - void setFinishTime(Long finishTime) { - this.totalDuration = finishTime - startTime; + void setFinishTime(Instant finishTime) { + this.totalDuration = Duration.between(startTime, finishTime); } - private void addResultToSubCount(SubCounts subCounts, Result.Type resultStatus) { + private void addResultToSubCount(SubCounts subCounts, Status resultStatus) { switch (resultStatus) { case FAILED: subCounts.failed++; @@ -219,7 +225,7 @@ private void addResultToSubCount(SubCounts subCounts, Result.Type resultStatus) } } - void addScenario(Result.Type resultStatus, String scenarioDesignation) { + void addScenario(Status resultStatus, String scenarioDesignation) { addResultToSubCount(scenarioSubCounts, resultStatus); switch (resultStatus) { case FAILED: diff --git a/core/src/main/java/cucumber/api/formatter/StrictAware.java b/core/src/main/java/io/cucumber/core/plugin/StrictAware.java similarity index 77% rename from core/src/main/java/cucumber/api/formatter/StrictAware.java rename to core/src/main/java/io/cucumber/core/plugin/StrictAware.java index 6faa35efdc..42b1992ee6 100755 --- a/core/src/main/java/cucumber/api/formatter/StrictAware.java +++ b/core/src/main/java/io/cucumber/core/plugin/StrictAware.java @@ -1,10 +1,11 @@ -package cucumber.api.formatter; +package io.cucumber.core.plugin; -import cucumber.api.Plugin; +import org.apiguardian.api.API; /** * Interface for Plugins that need to know if the Runtime is strict. */ +@API(status = API.Status.STABLE) public interface StrictAware extends Plugin { /** * When set to strict the plugin should indicate failure for undefined and pending steps diff --git a/core/src/main/java/cucumber/api/SummaryPrinter.java b/core/src/main/java/io/cucumber/core/plugin/SummaryPrinter.java similarity index 59% rename from core/src/main/java/cucumber/api/SummaryPrinter.java rename to core/src/main/java/io/cucumber/core/plugin/SummaryPrinter.java index a5ba643673..66a26755a7 100644 --- a/core/src/main/java/cucumber/api/SummaryPrinter.java +++ b/core/src/main/java/io/cucumber/core/plugin/SummaryPrinter.java @@ -1,10 +1,13 @@ -package cucumber.api; +package io.cucumber.core.plugin; + +import org.apiguardian.api.API; /** * Interface for plugins that print a summary after test execution. * * @see Plugin */ +@API(status = API.Status.STABLE) public interface SummaryPrinter extends Plugin { } diff --git a/core/src/main/java/cucumber/runtime/formatter/TestNGFormatter.java b/core/src/main/java/io/cucumber/core/plugin/TestNGFormatter.java similarity index 82% rename from core/src/main/java/cucumber/runtime/formatter/TestNGFormatter.java rename to core/src/main/java/io/cucumber/core/plugin/TestNGFormatter.java index 32f0dbaff5..5e5ab0045b 100644 --- a/core/src/main/java/cucumber/runtime/formatter/TestNGFormatter.java +++ b/core/src/main/java/io/cucumber/core/plugin/TestNGFormatter.java @@ -1,17 +1,16 @@ -package cucumber.runtime.formatter; - -import cucumber.api.PickleStepTestStep; -import cucumber.api.Result; -import cucumber.api.event.EventHandler; -import cucumber.api.event.EventListener; -import cucumber.api.event.EventPublisher; -import cucumber.api.event.TestCaseFinished; -import cucumber.api.event.TestCaseStarted; -import cucumber.api.event.TestRunFinished; -import cucumber.api.event.TestSourceRead; -import cucumber.api.event.TestStepFinished; -import cucumber.api.formatter.StrictAware; -import cucumber.runtime.CucumberException; +package io.cucumber.core.plugin; + +import io.cucumber.core.event.PickleStepTestStep; +import io.cucumber.core.event.EventHandler; +import io.cucumber.core.event.EventPublisher; +import io.cucumber.core.event.Result; +import io.cucumber.core.event.Status; +import io.cucumber.core.event.TestCaseFinished; +import io.cucumber.core.event.TestCaseStarted; +import io.cucumber.core.event.TestRunFinished; +import io.cucumber.core.event.TestSourceRead; +import io.cucumber.core.event.TestStepFinished; +import io.cucumber.core.exception.CucumberException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; @@ -34,13 +33,15 @@ import java.io.Writer; import java.net.URL; import java.text.SimpleDateFormat; +import java.time.Duration; import java.util.ArrayList; import java.util.Date; import java.util.List; -import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static java.time.Duration.ZERO; +import static java.util.Locale.ROOT; -final class TestNGFormatter implements EventListener, StrictAware { +public final class TestNGFormatter implements EventListener, StrictAware { private final Writer writer; private final Document document; @@ -118,30 +119,30 @@ public void setStrict(boolean strict) { } private void handleTestSourceRead(TestSourceRead event) { - testSources.addTestSourceReadEvent(event.uri, event); + testSources.addTestSourceReadEvent(event.getUri(), event); } private void handleTestCaseStarted(TestCaseStarted event) { - if (currentFeatureFile == null || !currentFeatureFile.equals(event.testCase.getUri())) { - currentFeatureFile = event.testCase.getUri(); + if (currentFeatureFile == null || !currentFeatureFile.equals(event.getTestCase().getUri())) { + currentFeatureFile = event.getTestCase().getUri(); previousTestCaseName = ""; exampleNumber = 1; clazz = document.createElement("class"); - clazz.setAttribute("name", testSources.getFeature(event.testCase.getUri()).getName()); + clazz.setAttribute("name", testSources.getFeature(event.getTestCase().getUri()).getName()); test.appendChild(clazz); } root = document.createElement("test-method"); clazz.appendChild(root); - testCase = new TestCase(event.testCase); + testCase = new TestCase(event.getTestCase()); testCase.start(root); } private void handleTestStepFinished(TestStepFinished event) { - if (event.testStep instanceof PickleStepTestStep) { - testCase.steps.add((PickleStepTestStep) event.testStep); - testCase.results.add(event.result); + if (event.getTestStep() instanceof PickleStepTestStep) { + testCase.steps.add((PickleStepTestStep) event.getTestStep()); + testCase.results.add(event.getResult()); } else { - testCase.hooks.add(event.result); + testCase.hooks.add(event.getResult()); } } @@ -217,10 +218,10 @@ final class TestCase { private final List steps = new ArrayList<>(); private final List results = new ArrayList<>(); private final List hooks = new ArrayList<>(); - private final cucumber.api.TestCase testCase; + private final io.cucumber.core.event.TestCase testCase; private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - TestCase(cucumber.api.TestCase testCase) { + TestCase(io.cucumber.core.event.TestCase testCase) { this.testCase = testCase; } @@ -229,7 +230,7 @@ private void start(Element element) { element.setAttribute("started-at", dateFormat.format(new Date())); } - private String calculateElementName(cucumber.api.TestCase testCase) { + private String calculateElementName(io.cucumber.core.event.TestCase testCase) { String testCaseName = testCase.getName(); if (testCaseName.equals(previousTestCaseName)) { return testCaseName + "_" + ++exampleNumber; @@ -248,23 +249,22 @@ void finish(Document doc, Element element) { Result skipped = null; Result failed = null; for (Result result : results) { - if (result.is(Result.Type.FAILED) || result.is(Result.Type.AMBIGUOUS)) { + if (result.getStatus().is(Status.FAILED) || result.getStatus().is(Status.AMBIGUOUS)) { failed = result; } - if (result.is(Result.Type.UNDEFINED) || result.is(Result.Type.PENDING)) { + if (result.getStatus().is(Status.UNDEFINED) || result.getStatus().is(Status.PENDING)) { skipped = result; } } for (Result result : hooks) { - if (failed == null && result.is(Result.Type.FAILED)) { + if (failed == null && result.getStatus().is(Status.FAILED)) { failed = result; } } if (failed != null) { element.setAttribute("status", "FAIL"); - StringWriter stringWriter = new StringWriter(); - failed.getError().printStackTrace(new PrintWriter(stringWriter)); - Element exception = createException(doc, failed.getError().getClass().getName(), stringBuilder.toString(), stringWriter.toString()); + String stacktrace = printStrackTrace(failed); + Element exception = createException(doc, failed.getError().getClass().getName(), stringBuilder.toString(), stacktrace); element.appendChild(exception); } else if (skipped != null) { if (strict) { @@ -279,15 +279,21 @@ void finish(Document doc, Element element) { } } + private String printStrackTrace(Result failed) { + StringWriter stringWriter = new StringWriter(); + failed.getError().printStackTrace(new PrintWriter(stringWriter)); + return stringWriter.toString(); + } + private String calculateTotalDurationString() { - long totalDurationNanos = 0; + Duration totalDuration = ZERO; for (Result r : results) { - totalDurationNanos += r.getDuration(); + totalDuration = totalDuration.plus(r.getDuration()); } for (Result r : hooks) { - totalDurationNanos += r.getDuration(); + totalDuration = totalDuration.plus(r.getDuration()); } - return String.valueOf(NANOSECONDS.toMillis(totalDurationNanos)); + return String.valueOf(totalDuration.toMillis()); } private void addStepAndResultListing(StringBuilder sb) { @@ -295,7 +301,7 @@ private void addStepAndResultListing(StringBuilder sb) { int length = sb.length(); String resultStatus = "not executed"; if (i < results.size()) { - resultStatus = results.get(i).getStatus().lowerCaseName(); + resultStatus = results.get(i).getStatus().name().toLowerCase(ROOT); } sb.append(getKeywordFromSource(steps.get(i).getStepLine())); sb.append(steps.get(i).getStepText()); diff --git a/core/src/main/java/cucumber/runtime/formatter/TestSourcesModel.java b/core/src/main/java/io/cucumber/core/plugin/TestSourcesModel.java similarity index 97% rename from core/src/main/java/cucumber/runtime/formatter/TestSourcesModel.java rename to core/src/main/java/io/cucumber/core/plugin/TestSourcesModel.java index e4598e54ef..735ec2b738 100644 --- a/core/src/main/java/cucumber/runtime/formatter/TestSourcesModel.java +++ b/core/src/main/java/io/cucumber/core/plugin/TestSourcesModel.java @@ -1,6 +1,6 @@ -package cucumber.runtime.formatter; +package io.cucumber.core.plugin; -import cucumber.api.event.TestSourceRead; +import io.cucumber.core.event.TestSourceRead; import gherkin.AstBuilder; import gherkin.GherkinDialect; import gherkin.GherkinDialectProvider; @@ -121,7 +121,7 @@ String getKeywordFromSource(String uri, int stepLine) { Feature feature = getFeature(uri); if (feature != null) { TestSourceRead event = getTestSourceReadEvent(uri); - String trimmedSourceLine = event.source.split("\n")[stepLine - 1].trim(); + String trimmedSourceLine = event.getSource().split("\n")[stepLine - 1].trim(); GherkinDialect dialect = new GherkinDialectProvider(feature.getLanguage()).getDefaultDialect(); for (String keyword : dialect.getStepKeywords()) { if (trimmedSourceLine.startsWith(keyword)) { @@ -154,7 +154,7 @@ private void parseGherkinSource(String path) { Parser parser = new Parser(new AstBuilder()); TokenMatcher matcher = new TokenMatcher(); try { - GherkinDocument gherkinDocument = parser.parse(pathToReadEventMap.get(path).source, matcher); + GherkinDocument gherkinDocument = parser.parse(pathToReadEventMap.get(path).getSource(), matcher); pathToAstMap.put(path, gherkinDocument); Map nodeMap = new HashMap(); AstNode currentParent = new AstNode(gherkinDocument.getFeature(), null); diff --git a/core/src/main/java/cucumber/runtime/formatter/TimelineFormatter.java b/core/src/main/java/io/cucumber/core/plugin/TimelineFormatter.java similarity index 81% rename from core/src/main/java/cucumber/runtime/formatter/TimelineFormatter.java rename to core/src/main/java/io/cucumber/core/plugin/TimelineFormatter.java index 5635c2b879..7c5f4eef64 100644 --- a/core/src/main/java/cucumber/runtime/formatter/TimelineFormatter.java +++ b/core/src/main/java/io/cucumber/core/plugin/TimelineFormatter.java @@ -1,20 +1,17 @@ -package cucumber.runtime.formatter; - -import cucumber.api.TestCase; -import cucumber.api.event.ConcurrentEventListener; -import cucumber.api.event.EventHandler; -import cucumber.api.event.EventPublisher; -import cucumber.api.event.TestCaseEvent; -import cucumber.api.event.TestCaseFinished; -import cucumber.api.event.TestCaseStarted; -import cucumber.api.event.TestRunFinished; -import cucumber.api.event.TestSourceRead; -import cucumber.api.formatter.NiceAppendable; -import cucumber.runtime.CucumberException; +package io.cucumber.core.plugin; + +import io.cucumber.core.event.TestCase; +import io.cucumber.core.event.EventHandler; +import io.cucumber.core.event.EventPublisher; +import io.cucumber.core.event.TestCaseEvent; +import io.cucumber.core.event.TestCaseFinished; +import io.cucumber.core.event.TestCaseStarted; +import io.cucumber.core.event.TestRunFinished; +import io.cucumber.core.event.TestSourceRead; +import io.cucumber.core.exception.CucumberException; import gherkin.deps.com.google.gson.Gson; import gherkin.deps.com.google.gson.GsonBuilder; import gherkin.deps.com.google.gson.annotations.SerializedName; -import gherkin.pickles.PickleTag; import java.io.Closeable; import java.io.File; @@ -24,32 +21,34 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.URL; +import java.time.Instant; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; -public class TimelineFormatter implements ConcurrentEventListener { +import static java.util.Locale.ROOT; + +public final class TimelineFormatter implements ConcurrentEventListener { - //TODO: if accepted then should move resources out into own project as per HTML report private static final String[] TEXT_ASSETS = new String[]{ - "/io/cucumber/formatter/timeline/index.html", - "/io/cucumber/formatter/timeline/formatter.js", - "/io/cucumber/formatter/timeline/report.css", - "/io/cucumber/formatter/timeline/jquery-3.3.1.min.js", - "/io/cucumber/formatter/timeline/vis.min.css", - "/io/cucumber/formatter/timeline/vis.min.js", - "/io/cucumber/formatter/timeline/vis.override.css", - "/io/cucumber/formatter/timeline/chosen.jquery.min.js", - "/io/cucumber/formatter/timeline/chosen.min.css", - "/io/cucumber/formatter/timeline/chosen.override.css", - "/io/cucumber/formatter/timeline/chosen-sprite.png" + "/io/cucumber/core/plugin/timeline/index.html", + "/io/cucumber/core/plugin/timeline/formatter.js", + "/io/cucumber/core/plugin/timeline/report.css", + "/io/cucumber/core/plugin/timeline/jquery-3.3.1.min.js", + "/io/cucumber/core/plugin/timeline/vis.min.css", + "/io/cucumber/core/plugin/timeline/vis.min.js", + "/io/cucumber/core/plugin/timeline/vis.override.css", + "/io/cucumber/core/plugin/timeline/chosen.jquery.min.js", + "/io/cucumber/core/plugin/timeline/chosen.min.css", + "/io/cucumber/core/plugin/timeline/chosen.override.css", + "/io/cucumber/core/plugin/timeline/chosen-sprite.png" }; private final EventHandler testSourceReadHandler = new EventHandler() { @Override public void receive(TestSourceRead event) { - testSources.addTestSourceReadEvent(event.uri, event); + testSources.addTestSourceReadEvent(event.getUri(), event); } }; private final EventHandler caseStartedHandler = new EventHandler() { @@ -204,9 +203,9 @@ class TestData { @SerializedName("scenario") final String scenario; @SerializedName("start") - final long startTime; + final Instant startTime; @SerializedName("end") - long endTime; + Instant endTime; @SerializedName("group") final long threadId; @SerializedName("content") @@ -222,22 +221,22 @@ class TestData { final String uri = testCase.getUri(); this.feature = TimelineFormatter.this.testSources.getFeatureName(uri); this.scenario = testCase.getName(); - this.startTime = started.getTimeStampMillis(); + this.startTime = started.getInstant(); this.threadId = threadId; this.tags = buildTagsValue(testCase); } private String buildTagsValue(final TestCase testCase) { final StringBuilder tags = new StringBuilder(); - for (final PickleTag tag : testCase.getTags()) { - tags.append(tag.getName().toLowerCase()).append(","); + for (final String tag : testCase.getTags()) { + tags.append(tag.toLowerCase()).append(","); } return tags.toString(); } public void end(final TestCaseFinished event) { - this.endTime = event.getTimeStampMillis(); - this.className = event.result.getStatus().lowerCaseName(); + this.endTime = event.getInstant(); + this.className = event.getResult().getStatus().name().toLowerCase(ROOT); } } diff --git a/core/src/main/java/cucumber/runtime/formatter/URLOutputStream.java b/core/src/main/java/io/cucumber/core/plugin/URLOutputStream.java similarity index 69% rename from core/src/main/java/cucumber/runtime/formatter/URLOutputStream.java rename to core/src/main/java/io/cucumber/core/plugin/URLOutputStream.java index 82f6752107..8d64b700da 100644 --- a/core/src/main/java/cucumber/runtime/formatter/URLOutputStream.java +++ b/core/src/main/java/io/cucumber/core/plugin/URLOutputStream.java @@ -1,13 +1,22 @@ -package cucumber.runtime.formatter; +package io.cucumber.core.plugin; import gherkin.deps.com.google.gson.Gson; -import cucumber.util.FixJava; -import java.io.*; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.Collections; import java.util.Map; +import java.util.stream.Collectors; + +import static java.nio.charset.StandardCharsets.UTF_8; + /** * A stream that can write to both file and http URLs. If it's a file URL, writes with a {@link java.io.FileOutputStream}, @@ -78,25 +87,24 @@ public void flush() throws IOException { @Override public void close() throws IOException { try { - if (urlConnection != null) { - int responseCode = urlConnection.getResponseCode(); - if (responseCode != expectedResponseCode) { - try { - urlConnection.getInputStream().close(); - throw new IOException(String.format("Expected response code: %d. Got: %d", expectedResponseCode, responseCode)); - } catch (IOException expected) { - InputStream errorStream = urlConnection.getErrorStream(); - if (errorStream != null) { - String responseBody = FixJava.readReader(new InputStreamReader(errorStream, "UTF-8")); - String contentType = urlConnection.getHeaderField("Content-Type"); - if (contentType == null) { - contentType = "text/plain"; - } - throw new ResponseException(responseBody, expected, responseCode, contentType); - } else { - throw expected; - } - } + if (urlConnection == null) { + return; + } + + int responseCode = urlConnection.getResponseCode(); + if (responseCode == expectedResponseCode) { + return; + } + + try { + urlConnection.getInputStream().close(); + throw new IOException(String.format("Expected response code: %d. Got: %d", expectedResponseCode, responseCode)); + } catch (IOException expected) { + InputStream errorStream = urlConnection.getErrorStream(); + if (errorStream != null) { + throw createResponseException(responseCode, expected, errorStream); + } else { + throw expected; } } } finally { @@ -104,6 +112,17 @@ public void close() throws IOException { } } + private ResponseException createResponseException(int responseCode, IOException expected, InputStream errorStream) throws IOException { + try (BufferedReader br = new BufferedReader(new InputStreamReader(errorStream, UTF_8))) { + String responseBody = br.lines().collect(Collectors.joining(System.lineSeparator())); + String contentType = urlConnection.getHeaderField("Content-Type"); + if (contentType == null) { + contentType = "text/plain"; + } + return new ResponseException(responseBody, expected, responseCode, contentType); + } + } + public class ResponseException extends IOException { private final Gson gson = new Gson(); private final int responseCode; diff --git a/core/src/main/java/cucumber/runtime/formatter/UTF8OutputStreamWriter.java b/core/src/main/java/io/cucumber/core/plugin/UTF8OutputStreamWriter.java similarity index 67% rename from core/src/main/java/cucumber/runtime/formatter/UTF8OutputStreamWriter.java rename to core/src/main/java/io/cucumber/core/plugin/UTF8OutputStreamWriter.java index 034a79aa0b..06d552f332 100644 --- a/core/src/main/java/cucumber/runtime/formatter/UTF8OutputStreamWriter.java +++ b/core/src/main/java/io/cucumber/core/plugin/UTF8OutputStreamWriter.java @@ -1,10 +1,10 @@ -package cucumber.runtime.formatter; +package io.cucumber.core.plugin; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.nio.charset.Charset; -class UTF8OutputStreamWriter extends OutputStreamWriter { +final class UTF8OutputStreamWriter extends OutputStreamWriter { UTF8OutputStreamWriter(OutputStream out) { super(out, Charset.forName("UTF-8")); } diff --git a/core/src/main/java/cucumber/runtime/UndefinedStepsTracker.java b/core/src/main/java/io/cucumber/core/plugin/UndefinedStepsTracker.java similarity index 80% rename from core/src/main/java/cucumber/runtime/UndefinedStepsTracker.java rename to core/src/main/java/io/cucumber/core/plugin/UndefinedStepsTracker.java index 807fa5280a..5401528041 100644 --- a/core/src/main/java/cucumber/runtime/UndefinedStepsTracker.java +++ b/core/src/main/java/io/cucumber/core/plugin/UndefinedStepsTracker.java @@ -1,10 +1,9 @@ -package cucumber.runtime; +package io.cucumber.core.plugin; -import cucumber.api.event.EventHandler; -import cucumber.api.event.EventListener; -import cucumber.api.event.EventPublisher; -import cucumber.api.event.SnippetsSuggestedEvent; -import cucumber.api.event.TestSourceRead; +import io.cucumber.core.event.EventHandler; +import io.cucumber.core.event.EventPublisher; +import io.cucumber.core.event.SnippetsSuggestedEvent; +import io.cucumber.core.event.TestSourceRead; import gherkin.AstBuilder; import gherkin.GherkinDialect; import gherkin.GherkinDialectProvider; @@ -16,30 +15,29 @@ import gherkin.ast.GherkinDocument; import gherkin.ast.ScenarioDefinition; import gherkin.ast.Step; -import gherkin.pickles.PickleLocation; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -public class UndefinedStepsTracker implements EventListener { - private final List snippets = new ArrayList(); +final class UndefinedStepsTracker implements EventListener { + private final List snippets = new ArrayList<>(); private final IGherkinDialectProvider dialectProvider = new GherkinDialectProvider(); - private final Map pathToSourceMap = new HashMap(); - private final Map pathToStepMap = new HashMap(); + private final Map pathToSourceMap = new HashMap<>(); + private final Map pathToStepMap = new HashMap<>(); private boolean hasUndefinedSteps = false; private EventHandler testSourceReadHandler = new EventHandler() { @Override public void receive(TestSourceRead event) { - pathToSourceMap.put(event.uri, event.source); + pathToSourceMap.put(event.getUri(), event.getSource()); } }; private EventHandler snippetsSuggestedHandler = new EventHandler() { @Override public void receive(SnippetsSuggestedEvent event) { - handleSnippetsSuggested(event.uri, event.stepLocations, event.snippets); + handleSnippetsSuggested(event.getUri(), event.getStepLocations(), event.getSnippets()); } }; @@ -49,15 +47,15 @@ public void setEventPublisher(EventPublisher publisher) { publisher.registerHandlerFor(SnippetsSuggestedEvent.class, snippetsSuggestedHandler); } - public boolean hasUndefinedSteps() { + boolean hasUndefinedSteps() { return hasUndefinedSteps; } - public List getSnippets() { + List getSnippets() { return snippets; } - void handleSnippetsSuggested(String uri, List stepLocations, List snippets) { + void handleSnippetsSuggested(String uri, List stepLocations, List snippets) { hasUndefinedSteps = true; String keyword = givenWhenThenKeyword(uri, stepLocations); for (String rawSnippet : snippets) { @@ -68,7 +66,7 @@ void handleSnippetsSuggested(String uri, List stepLocations, Lis } } - private String givenWhenThenKeyword(String uri, List stepLocations) { + private String givenWhenThenKeyword(String uri, List stepLocations) { String keyword = null; if (!stepLocations.isEmpty()) { if (pathToSourceMap.containsKey(uri)) { @@ -78,7 +76,7 @@ private String givenWhenThenKeyword(String uri, List stepLocatio return keyword != null ? keyword : getFirstGivenKeyword(dialectProvider.getDefaultDialect()); } - private String getKeywordFromSource(String path, List stepLocations) { + private String getKeywordFromSource(String path, List stepLocations) { if (!pathToStepMap.containsKey(path)) { createFeatureStepMap(path); } @@ -88,13 +86,13 @@ private String getKeywordFromSource(String path, List stepLocati GherkinDialect featureDialect = pathToStepMap.get(path).dialect; List givenThenWhenKeywords = getGivenWhenThenKeywords(featureDialect); Map stepMap = pathToStepMap.get(path).stepMap; - for (PickleLocation stepLocation : stepLocations) { + for (SnippetsSuggestedEvent.Location stepLocation : stepLocations) { if (!stepMap.containsKey(stepLocation.getLine())) { continue; } for (StepNode stepNode = stepMap.get(stepLocation.getLine()); stepNode != null; stepNode = stepNode.previous) { for (String keyword : givenThenWhenKeywords) { - if (!keyword.equals("* ") && keyword == stepNode.step.getKeyword()) { + if (!keyword.equals("* ") && keyword.equals(stepNode.step.getKeyword())) { return convertToCodeKeyword(keyword); } } @@ -107,11 +105,11 @@ private void createFeatureStepMap(String path) { if (!pathToSourceMap.containsKey(path)) { return; } - Parser parser = new Parser(new AstBuilder()); + Parser parser = new Parser<>(new AstBuilder()); TokenMatcher matcher = new TokenMatcher(); try { GherkinDocument gherkinDocument = parser.parse(pathToSourceMap.get(path), matcher); - Map stepMap = new HashMap(); + Map stepMap = new HashMap<>(); StepNode initialPreviousNode = null; for (ScenarioDefinition child : gherkinDocument.getFeature().getChildren()) { StepNode lastStepNode = processScenarioDefinition(stepMap, initialPreviousNode, child); @@ -136,7 +134,7 @@ private StepNode processScenarioDefinition(Map stepMap, StepN } private List getGivenWhenThenKeywords(GherkinDialect dialect) { - List keywords = new ArrayList(); + List keywords = new ArrayList<>(); keywords.addAll(dialect.getGivenKeywords()); keywords.addAll(dialect.getWhenKeywords()); keywords.addAll(dialect.getThenKeywords()); diff --git a/core/src/main/java/cucumber/runtime/formatter/UnusedStepsSummaryPrinter.java b/core/src/main/java/io/cucumber/core/plugin/UnusedStepsSummaryPrinter.java similarity index 79% rename from core/src/main/java/cucumber/runtime/formatter/UnusedStepsSummaryPrinter.java rename to core/src/main/java/io/cucumber/core/plugin/UnusedStepsSummaryPrinter.java index 67e4ababb1..e0772f2b75 100644 --- a/core/src/main/java/cucumber/runtime/formatter/UnusedStepsSummaryPrinter.java +++ b/core/src/main/java/io/cucumber/core/plugin/UnusedStepsSummaryPrinter.java @@ -1,27 +1,25 @@ -package cucumber.runtime.formatter; +package io.cucumber.core.plugin; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; -import cucumber.api.Result; -import cucumber.api.SummaryPrinter; -import cucumber.api.event.*; -import cucumber.api.formatter.ColorAware; -import cucumber.api.formatter.NiceAppendable; +import io.cucumber.core.event.*; + +import static java.util.Locale.ROOT; public class UnusedStepsSummaryPrinter implements ColorAware, EventListener, SummaryPrinter { private EventHandler stepDefinedHandler = new EventHandler() { @Override public void receive(StepDefinedEvent event) { - unusedSteps.put(event.stepDefinition.getLocation(false), event.stepDefinition.getPattern()); + unusedSteps.put(event.getStepDefinition().getLocation(false), event.getStepDefinition().getPattern()); } }; private EventHandler testStepFinishedHandler = new EventHandler() { @Override public void receive(TestStepFinished event) { - String codeLocation = event.testStep.getCodeLocation(); + String codeLocation = event.getTestStep().getCodeLocation(); if (codeLocation != null) { unusedSteps.remove(codeLocation); } @@ -34,7 +32,7 @@ public void receive(TestRunFinished event) { return; } - Format format = formats.get(Result.Type.UNUSED.lowerCaseName()); + Format format = formats.get(Status.UNUSED.name().toLowerCase(ROOT)); out.println(format.text(unusedSteps.size() + " Unused steps:")); // Output results when done @@ -50,7 +48,7 @@ public void receive(TestRunFinished event) { private final NiceAppendable out; private Formats formats = new MonochromeFormats(); - @SuppressWarnings("WeakerAccess") // Used by PluginFactory + @SuppressWarnings("WeakerAccess") public UnusedStepsSummaryPrinter(Appendable out) { this.out = new NiceAppendable(out); } diff --git a/core/src/main/java/cucumber/runtime/formatter/UsageFormatter.java b/core/src/main/java/io/cucumber/core/plugin/UsageFormatter.java similarity index 60% rename from core/src/main/java/cucumber/runtime/formatter/UsageFormatter.java rename to core/src/main/java/io/cucumber/core/plugin/UsageFormatter.java index 3db7b4798c..cc0c9b0352 100644 --- a/core/src/main/java/cucumber/runtime/formatter/UsageFormatter.java +++ b/core/src/main/java/io/cucumber/core/plugin/UsageFormatter.java @@ -1,31 +1,32 @@ -package cucumber.runtime.formatter; - -import cucumber.api.PickleStepTestStep; -import cucumber.api.Plugin; -import cucumber.api.Result; -import cucumber.api.event.EventHandler; -import cucumber.api.event.EventListener; -import cucumber.api.event.EventPublisher; -import cucumber.api.event.TestRunFinished; -import cucumber.api.event.TestStepFinished; -import cucumber.api.formatter.NiceAppendable; +package io.cucumber.core.plugin; + import gherkin.deps.com.google.gson.Gson; import gherkin.deps.com.google.gson.GsonBuilder; - -import java.math.BigDecimal; -import java.math.RoundingMode; +import gherkin.deps.com.google.gson.JsonPrimitive; +import gherkin.deps.com.google.gson.JsonSerializer; +import io.cucumber.core.event.EventHandler; +import io.cucumber.core.event.EventPublisher; +import io.cucumber.core.event.PickleStepTestStep; +import io.cucumber.core.event.Result; +import io.cucumber.core.event.Status; +import io.cucumber.core.event.TestRunFinished; +import io.cucumber.core.event.TestStepFinished; + +import java.time.Duration; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; /** * Formatter to measure performance of steps. Includes average and median step duration. */ -final class UsageFormatter implements Plugin, EventListener { - private static final BigDecimal NANOS_PER_SECOND = BigDecimal.valueOf(1000000000); +public final class UsageFormatter implements Plugin, EventListener { + + private static final long NANOS_PER_SECOND = TimeUnit.SECONDS.toNanos(1); final Map> usageMap = new LinkedHashMap<>(); private final NiceAppendable out; @@ -59,9 +60,9 @@ public void setEventPublisher(EventPublisher publisher) { } void handleTestStepFinished(TestStepFinished event) { - if (event.testStep instanceof PickleStepTestStep && event.result.is(Result.Type.PASSED)) { - PickleStepTestStep testStep = (PickleStepTestStep) event.testStep; - addUsageEntry(event.result, testStep); + if (event.getTestStep() instanceof PickleStepTestStep && event.getResult().getStatus().is(Status.PASSED)) { + PickleStepTestStep testStep = (PickleStepTestStep) event.getTestStep(); + addUsageEntry(event.getResult(), testStep); } } @@ -86,25 +87,21 @@ private List createStepContainers(List stepContain return stepContainers; } - private Map createAggregatedDurations(StepContainer stepContainer) { - Map aggregatedResults = new LinkedHashMap<>(); - List rawDurations = getRawDurations(stepContainer.getDurations()); + private Map createAggregatedDurations(StepContainer stepContainer) { + Map aggregatedResults = new LinkedHashMap<>(); + List rawDurations = getRawDurations(stepContainer.getDurations()); - BigDecimal average = calculateAverage(rawDurations); + Duration average = calculateAverage(rawDurations); aggregatedResults.put("average", average); - BigDecimal median = calculateMedian(rawDurations); + Duration median = calculateMedian(rawDurations); aggregatedResults.put("median", median); return aggregatedResults; } - private BigDecimal toSeconds(Long nanoSeconds) { - return BigDecimal.valueOf(nanoSeconds).divide(NANOS_PER_SECOND); - } - - private List getRawDurations(List stepDurations) { - List rawDurations = new ArrayList<>(); + private List getRawDurations(List stepDurations) { + List rawDurations = new ArrayList<>(); for (StepDuration stepDuration : stepDurations) { rawDurations.add(stepDuration.duration); @@ -113,30 +110,22 @@ private List getRawDurations(List stepDurations) { } private Gson gson() { - return new GsonBuilder().setPrettyPrinting().create(); + JsonSerializer durationJsonSerializer = (duration, returnVal, jsonSerializationContext) -> + new JsonPrimitive((double) duration.getNano() / NANOS_PER_SECOND); + + return new GsonBuilder() + .registerTypeAdapter(Duration.class, durationJsonSerializer) + .setPrettyPrinting() + .create(); } private void addUsageEntry(Result result, PickleStepTestStep testStep) { - List stepContainers = findOrCreateUsageEntry(testStep.getPattern()); + List stepContainers = usageMap.computeIfAbsent(testStep.getPattern(), k -> new ArrayList<>()); StepContainer stepContainer = findOrCreateStepContainer(testStep.getStepText(), stepContainers); - StepDuration stepDuration = createStepDuration(result.getDuration(), testStep.getStepLocation()); + StepDuration stepDuration = new StepDuration(result.getDuration(), testStep.getStepLocation()); stepContainer.getDurations().add(stepDuration); } - private List findOrCreateUsageEntry(String stepDefinition) { - List stepContainers = usageMap.get(stepDefinition); - if (stepContainers == null) { - stepContainers = new ArrayList<>(); - usageMap.put(stepDefinition, stepContainers); - } - return stepContainers; - } - - private StepDuration createStepDuration(Long duration, String location) { - StepDuration stepDuration = new StepDuration(toSeconds(duration), location); - return stepDuration; - } - private StepContainer findOrCreateStepContainer(String stepNameWithArgs, List stepContainers) { for (StepContainer container : stepContainers) { if (stepNameWithArgs.equals(container.getName())) { @@ -148,6 +137,39 @@ private StepContainer findOrCreateStepContainer(String stepNameWithArgs, List durationEntries) { + + Duration sum = Duration.ZERO; + for (Duration duration : durationEntries) { + sum = sum.plus(duration); + } + if (sum.isZero()) { + return Duration.ZERO; + } + + return sum.dividedBy(durationEntries.size()); + } + + /** + * Calculate the median of a list of duration entries + */ + Duration calculateMedian(List durationEntries) { + if (durationEntries.isEmpty()) { + return Duration.ZERO; + } + Collections.sort(durationEntries); + int middle = durationEntries.size() / 2; + if (durationEntries.size() % 2 == 1) { + return durationEntries.get(middle); + } else { + Duration total = durationEntries.get(middle - 1).plus(durationEntries.get(middle)); + return total.dividedBy(2); + } + } + /** * Container of Step Definitions (patterns) */ @@ -181,7 +203,7 @@ public List getSteps() { */ static class StepContainer { private final String name; - private final Map aggregatedDurations = new HashMap<>(); + private final Map aggregatedDurations = new HashMap<>(); private final List durations = new ArrayList<>(); StepContainer(String name) { @@ -192,11 +214,11 @@ public String getName() { return name; } - void putAllAggregatedDurations(Map aggregatedDurations) { + void putAllAggregatedDurations(Map aggregatedDurations) { this.aggregatedDurations.putAll(aggregatedDurations); } - public Map getAggregatedDurations() { + public Map getAggregatedDurations() { return aggregatedDurations; } @@ -207,15 +229,15 @@ List getDurations() { } static class StepDuration { - private final BigDecimal duration; + private final Duration duration; private final String location; - StepDuration(BigDecimal duration, String location) { + StepDuration(Duration duration, String location) { this.duration = duration; this.location = location; } - public BigDecimal getDuration() { + public Duration getDuration() { return duration; } @@ -223,36 +245,4 @@ public String getLocation() { return location; } } - - /** - * Calculate the average of a list of duration entries - */ - BigDecimal calculateAverage(List durationEntries) { - if (durationEntries.isEmpty()) { - return BigDecimal.ZERO; - } - - BigDecimal sum = BigDecimal.valueOf(0L); - for (BigDecimal duration : durationEntries) { - sum = sum.add(duration); - } - return sum.divide(BigDecimal.valueOf(durationEntries.size()), 9, RoundingMode.HALF_UP); - } - - /** - * Calculate the median of a list of duration entries - */ - BigDecimal calculateMedian(List durationEntries) { - if (durationEntries.isEmpty()) { - return BigDecimal.ZERO; - } - Collections.sort(durationEntries); - int middle = durationEntries.size() / 2; - if (durationEntries.size() % 2 == 1) { - return durationEntries.get(middle); - } else { - BigDecimal total = durationEntries.get(middle - 1).add(durationEntries.get(middle)); - return total.divide(BigDecimal.valueOf(2.0), 9, RoundingMode.HALF_UP); - } - } } diff --git a/core/src/main/java/cucumber/runtime/MethodFormat.java b/core/src/main/java/io/cucumber/core/reflection/MethodFormat.java similarity index 96% rename from core/src/main/java/cucumber/runtime/MethodFormat.java rename to core/src/main/java/io/cucumber/core/reflection/MethodFormat.java index cd7ad4cb6d..c1eea722ae 100644 --- a/core/src/main/java/cucumber/runtime/MethodFormat.java +++ b/core/src/main/java/io/cucumber/core/reflection/MethodFormat.java @@ -1,4 +1,6 @@ -package cucumber.runtime; +package io.cucumber.core.reflection; + +import io.cucumber.core.exception.CucumberException; import java.lang.reflect.Method; import java.security.ProtectionDomain; @@ -9,9 +11,9 @@ /** * Helper class for formatting a method signature to a shorter form. */ -public class MethodFormat { +public final class MethodFormat { private static final Pattern METHOD_PATTERN = Pattern.compile("((?:static\\s|public\\s)+)([^\\s]*)\\s\\.?(.*)\\.([^\\(]*)\\(([^\\)]*)\\)(?: throws )?(.*)"); - private static final Pattern PACKAGE_PATTERN = Pattern.compile("[^,]*\\."); + private static final Pattern PACKAGE_PATTERN = Pattern.compile("[^,<>]*\\."); private final MessageFormat format; public static final MethodFormat SHORT = new MethodFormat("%c.%m(%a)"); diff --git a/core/src/main/java/cucumber/runtime/NoInstancesException.java b/core/src/main/java/io/cucumber/core/reflection/NoInstancesException.java similarity index 50% rename from core/src/main/java/cucumber/runtime/NoInstancesException.java rename to core/src/main/java/io/cucumber/core/reflection/NoInstancesException.java index 152e545a6d..e7a587f737 100644 --- a/core/src/main/java/cucumber/runtime/NoInstancesException.java +++ b/core/src/main/java/io/cucumber/core/reflection/NoInstancesException.java @@ -1,8 +1,10 @@ -package cucumber.runtime; +package io.cucumber.core.reflection; -public class NoInstancesException extends CucumberException { +import io.cucumber.core.exception.CucumberException; - public NoInstancesException(Class parentType) { +public final class NoInstancesException extends CucumberException { + + NoInstancesException(Class parentType) { super(createMessage(parentType)); } diff --git a/core/src/main/java/cucumber/runtime/Reflections.java b/core/src/main/java/io/cucumber/core/reflection/Reflections.java similarity index 77% rename from core/src/main/java/cucumber/runtime/Reflections.java rename to core/src/main/java/io/cucumber/core/reflection/Reflections.java index 7f4dce61a2..9bc490dfdc 100644 --- a/core/src/main/java/cucumber/runtime/Reflections.java +++ b/core/src/main/java/io/cucumber/core/reflection/Reflections.java @@ -1,19 +1,28 @@ -package cucumber.runtime; +package io.cucumber.core.reflection; + +import io.cucumber.core.io.ClassFinder; +import io.cucumber.core.exception.CucumberException; import java.lang.reflect.Constructor; import java.net.URI; +import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; -public class Reflections { +public final class Reflections { private final ClassFinder classFinder; public Reflections(ClassFinder classFinder) { this.classFinder = classFinder; } + public static boolean isInstantiable(Class clazz) { + boolean isNonStaticInnerClass = !Modifier.isStatic(clazz.getModifiers()) && clazz.getEnclosingClass() != null; + return Modifier.isPublic(clazz.getModifiers()) && !Modifier.isAbstract(clazz.getModifiers()) && !isNonStaticInnerClass; + } + public T instantiateExactlyOneSubclass(Class parentType, List packageNames, Class[] constructorParams, Object[] constructorArgs, T fallback) { Collection instances = instantiateSubclasses(parentType, packageNames, constructorParams, constructorArgs); if (instances.size() == 1) { @@ -32,7 +41,7 @@ public Collection instantiateSubclasses(Class parentType, Li Collection result = new HashSet(); for (URI packageName : packageNames) { for (Class clazz : classFinder.getDescendants(parentType, packageName)) { - if (Utils.isInstantiable(clazz)) { + if (isInstantiable(clazz)) { result.add(newInstance(constructorParams, constructorArgs, clazz)); } } @@ -41,7 +50,7 @@ public Collection instantiateSubclasses(Class parentType, Li } public T newInstance(Class[] constructorParams, Object[] constructorArgs, Class clazz) { - Constructor constructor = null; + Constructor constructor; try { constructor = clazz.getConstructor(constructorParams); try { @@ -55,14 +64,5 @@ public T newInstance(Class[] constructorParams, Object[] constructorArgs, Cl } } - private boolean hasConstructor(Class clazz, Class[] paramTypes) { - try { - clazz.getConstructor(paramTypes); - return true; - } catch (NoSuchMethodException e) { - return false; - } - } - } diff --git a/core/src/main/java/io/cucumber/core/reflection/TooManyInstancesException.java b/core/src/main/java/io/cucumber/core/reflection/TooManyInstancesException.java new file mode 100644 index 0000000000..dd5896a290 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/reflection/TooManyInstancesException.java @@ -0,0 +1,16 @@ +package io.cucumber.core.reflection; + +import io.cucumber.core.exception.CucumberException; + +import java.util.Collection; + +public final class TooManyInstancesException extends CucumberException { + + TooManyInstancesException(Collection instances) { + super(createMessage(instances)); + } + + private static String createMessage(Collection instances) { + return "Expected only one instance, but found too many: " + instances; + } +} diff --git a/core/src/main/java/cucumber/runner/AmbiguousPickleStepDefinitionsMatch.java b/core/src/main/java/io/cucumber/core/runner/AmbiguousPickleStepDefinitionsMatch.java similarity index 85% rename from core/src/main/java/cucumber/runner/AmbiguousPickleStepDefinitionsMatch.java rename to core/src/main/java/io/cucumber/core/runner/AmbiguousPickleStepDefinitionsMatch.java index 4593e937e2..6ee81e9143 100644 --- a/core/src/main/java/cucumber/runner/AmbiguousPickleStepDefinitionsMatch.java +++ b/core/src/main/java/io/cucumber/core/runner/AmbiguousPickleStepDefinitionsMatch.java @@ -1,8 +1,8 @@ -package cucumber.runner; +package io.cucumber.core.runner; -import cucumber.api.Scenario; +import io.cucumber.core.api.Scenario; import gherkin.pickles.PickleStep; -import io.cucumber.stepexpression.Argument; +import io.cucumber.core.stepexpression.Argument; import java.util.Collections; diff --git a/core/src/main/java/cucumber/runner/AmbiguousStepDefinitionsException.java b/core/src/main/java/io/cucumber/core/runner/AmbiguousStepDefinitionsException.java similarity index 92% rename from core/src/main/java/cucumber/runner/AmbiguousStepDefinitionsException.java rename to core/src/main/java/io/cucumber/core/runner/AmbiguousStepDefinitionsException.java index ad15ac486e..e136e787fd 100644 --- a/core/src/main/java/cucumber/runner/AmbiguousStepDefinitionsException.java +++ b/core/src/main/java/io/cucumber/core/runner/AmbiguousStepDefinitionsException.java @@ -1,6 +1,6 @@ -package cucumber.runner; +package io.cucumber.core.runner; -import cucumber.runtime.CucumberException; +import io.cucumber.core.exception.CucumberException; import gherkin.pickles.PickleStep; import java.util.List; diff --git a/core/src/main/java/io/cucumber/core/runner/CachingGlue.java b/core/src/main/java/io/cucumber/core/runner/CachingGlue.java new file mode 100644 index 0000000000..3db3379fa7 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/runner/CachingGlue.java @@ -0,0 +1,277 @@ +package io.cucumber.core.runner; + +import gherkin.pickles.PickleStep; +import io.cucumber.core.backend.DataTableTypeDefinition; +import io.cucumber.core.backend.DefaultDataTableCellTransformerDefinition; +import io.cucumber.core.backend.DefaultDataTableEntryTransformerDefinition; +import io.cucumber.core.backend.DefaultParameterTransformerDefinition; +import io.cucumber.core.backend.Glue; +import io.cucumber.core.backend.HookDefinition; +import io.cucumber.core.backend.ParameterTypeDefinition; +import io.cucumber.core.backend.StepDefinition; +import io.cucumber.core.event.StepDefinedEvent; +import io.cucumber.core.eventbus.EventBus; +import io.cucumber.core.stepexpression.Argument; +import io.cucumber.core.stepexpression.TypeRegistry; +import io.cucumber.cucumberexpressions.ParameterByTypeTransformer; +import io.cucumber.datatable.TableCellByTypeTransformer; +import io.cucumber.datatable.TableEntryByTypeTransformer; + +import java.util.*; + +final class CachingGlue implements Glue { + private static final Comparator ASCENDING = Comparator.comparing(HookDefinition::getOrder); + private static final Comparator DESCENDING = ASCENDING.reversed(); + + private final List parameterTypeDefinitions = new ArrayList<>(); + private final List dataTableTypeDefinitions = new ArrayList<>(); + private final List defaultParameterTransformers = new ArrayList<>(); + private final List defaultDataTableEntryTransformers = new ArrayList<>(); + private final List defaultDataTableCellTransformers = new ArrayList<>(); + + private final List beforeHooks = new ArrayList<>(); + private final List beforeStepHooks = new ArrayList<>(); + private final List stepDefinitions = new ArrayList<>(); + private final List afterStepHooks = new ArrayList<>(); + private final List afterHooks = new ArrayList<>(); + + /* + * Storing the pattern that matches the step text allows us to cache the rather slow + * regex comparisons in `stepDefinitionMatches`. + * This cache does not need to be cleaned. The matching pattern be will used to look + * up a pickle specific step definition from `stepDefinitionsByPattern`. + */ + private final Map stepPatternByStepText = new HashMap<>(); + private final Map stepDefinitionsByPattern = new TreeMap<>(); + + private final EventBus bus; + + + CachingGlue(EventBus bus) { + this.bus = bus; + } + + @Override + public void addStepDefinition(StepDefinition stepDefinition) { + stepDefinitions.add(stepDefinition); + } + + @Override + public void addBeforeHook(HookDefinition hookDefinition) { + beforeHooks.add(hookDefinition); + beforeHooks.sort(ASCENDING); + } + + @Override + public void addBeforeStepHook(HookDefinition hookDefinition) { + beforeStepHooks.add(hookDefinition); + beforeStepHooks.sort(ASCENDING); + } + + @Override + public void addAfterHook(HookDefinition hookDefinition) { + afterHooks.add(hookDefinition); + afterHooks.sort(DESCENDING); + } + + @Override + public void addAfterStepHook(HookDefinition hookDefinition) { + afterStepHooks.add(hookDefinition); + afterStepHooks.sort(DESCENDING); + } + + @Override + public void addParameterType(ParameterTypeDefinition parameterTypeDefinition) { + parameterTypeDefinitions.add(parameterTypeDefinition); + } + + @Override + public void addDataTableType(DataTableTypeDefinition dataTableTypeDefinition) { + dataTableTypeDefinitions.add(dataTableTypeDefinition); + } + + @Override + public void addDefaultParameterTransformer(DefaultParameterTransformerDefinition defaultParameterTransformer) { + defaultParameterTransformers.add(defaultParameterTransformer); + } + + @Override + public void addDefaultDataTableEntryTransformer(DefaultDataTableEntryTransformerDefinition defaultDataTableEntryTransformer) { + defaultDataTableEntryTransformers.add(defaultDataTableEntryTransformer); + } + + @Override + public void addDefaultDataTableCellTransformer(DefaultDataTableCellTransformerDefinition defaultDataTableCellTransformer) { + defaultDataTableCellTransformers.add(defaultDataTableCellTransformer); + } + + Collection getBeforeHooks() { + return beforeHooks; + } + + Collection getBeforeStepHooks() { + return beforeStepHooks; + } + + Collection getAfterHooks() { + return afterHooks; + } + + Collection getAfterStepHooks() { + return afterStepHooks; + } + + Collection getParameterTypeDefinitions() { + return parameterTypeDefinitions; + } + + Collection getDataTableTypeDefinitions() { + return dataTableTypeDefinitions; + } + + Collection getStepDefinitions() { + return stepDefinitions; + } + + Map getStepPatternByStepText() { + return stepPatternByStepText; + } + + Map getStepDefinitionsByPattern() { + return stepDefinitionsByPattern; + } + + Collection getDefaultParameterTransformers() { + return defaultParameterTransformers; + } + + Collection getDefaultDataTableEntryTransformers() { + return defaultDataTableEntryTransformers; + } + + Collection getDefaultDataTableCellTransformers() { + return defaultDataTableCellTransformers; + } + + void prepareGlue(TypeRegistry typeRegistry) throws DuplicateStepDefinitionException { + parameterTypeDefinitions.forEach(ptd -> typeRegistry.defineParameterType(ptd.parameterType())); + dataTableTypeDefinitions.forEach(dtd -> typeRegistry.defineDataTableType(dtd.dataTableType())); + + if (defaultParameterTransformers.size() == 1) { + DefaultParameterTransformerDefinition definition = defaultParameterTransformers.get(0); + ParameterByTypeTransformer transformer = definition.parameterByTypeTransformer(); + typeRegistry.setDefaultParameterTransformer(transformer); + } else if (defaultParameterTransformers.size() > 1) { + throw new DuplicateDefaultParameterTransformers(defaultParameterTransformers); + } + + if (defaultDataTableEntryTransformers.size() == 1) { + DefaultDataTableEntryTransformerDefinition definition = defaultDataTableEntryTransformers.get(0); + TableEntryByTypeTransformer transformer = definition.tableEntryByTypeTransformer(); + typeRegistry.setDefaultDataTableEntryTransformer(transformer); + } else if (defaultDataTableEntryTransformers.size() > 1) { + throw new DuplicateDefaultDataTableEntryTransformers(defaultDataTableEntryTransformers); + } + + + if (defaultDataTableCellTransformers.size() == 1) { + DefaultDataTableCellTransformerDefinition definition = defaultDataTableCellTransformers.get(0); + TableCellByTypeTransformer transformer = definition.tableCellByTypeTransformer(); + typeRegistry.setDefaultDataTableCellTransformer(transformer); + } else if (defaultDataTableCellTransformers.size() > 1) { + throw new DuplicateDefaultDataTableCellTransformers(defaultDataTableCellTransformers); + } + + stepDefinitions.forEach(stepDefinition -> { + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, typeRegistry); + CoreStepDefinition previous = stepDefinitionsByPattern.get(stepDefinition.getPattern()); + if (previous != null) { + throw new DuplicateStepDefinitionException(previous.getStepDefinition(), stepDefinition); + } + stepDefinitionsByPattern.put(coreStepDefinition.getPattern(), coreStepDefinition); + bus.send(new StepDefinedEvent(bus.getInstant(), stepDefinition)); + }); + } + + PickleStepDefinitionMatch stepDefinitionMatch(String featurePath, PickleStep step) { + PickleStepDefinitionMatch cachedMatch = cachedStepDefinitionMatch(featurePath, step); + if (cachedMatch != null) { + return cachedMatch; + } + return findStepDefinitionMatch(featurePath, step); + } + + + private PickleStepDefinitionMatch cachedStepDefinitionMatch(String featurePath, PickleStep step) { + String stepDefinitionPattern = stepPatternByStepText.get(step.getText()); + if (stepDefinitionPattern == null) { + return null; + } + + CoreStepDefinition coreStepDefinition = stepDefinitionsByPattern.get(stepDefinitionPattern); + if (coreStepDefinition == null) { + return null; + } + + // Step definition arguments consists of parameters included in the step text and + // gherkin step arguments (doc string and data table) which are not included in + // the step text. As such the step definition arguments can not be cached and + // must be recreated each time. + List arguments = coreStepDefinition.matchedArguments(step); + return new PickleStepDefinitionMatch(arguments, coreStepDefinition.getStepDefinition(), featurePath, step); + } + + private PickleStepDefinitionMatch findStepDefinitionMatch(String featurePath, PickleStep step) { + List matches = stepDefinitionMatches(featurePath, step); + if (matches.isEmpty()) { + return null; + } + if (matches.size() > 1) { + throw new AmbiguousStepDefinitionsException(step, matches); + } + + PickleStepDefinitionMatch match = matches.get(0); + + stepPatternByStepText.put(step.getText(), match.getPattern()); + + return match; + } + + private List stepDefinitionMatches(String featurePath, PickleStep step) { + List result = new ArrayList<>(); + for (CoreStepDefinition coreStepDefinition : stepDefinitionsByPattern.values()) { + List arguments = coreStepDefinition.matchedArguments(step); + if (arguments != null) { + result.add(new PickleStepDefinitionMatch(arguments, coreStepDefinition.getStepDefinition(), featurePath, step)); + } + } + return result; + } + + void removeScenarioScopedGlue() { + stepDefinitionsByPattern.clear(); + removeScenarioScopedGlue(beforeHooks); + removeScenarioScopedGlue(beforeStepHooks); + removeScenarioScopedGlue(afterHooks); + removeScenarioScopedGlue(afterStepHooks); + removeScenarioScopedGlue(stepDefinitions); + removeScenarioScopedGlue(dataTableTypeDefinitions); + removeScenarioScopedGlue(parameterTypeDefinitions); + removeScenarioScopedGlue(defaultParameterTransformers); + removeScenarioScopedGlue(defaultDataTableEntryTransformers); + removeScenarioScopedGlue(defaultDataTableCellTransformers); + } + + private void removeScenarioScopedGlue(Iterable glues) { + Iterator glueIterator = glues.iterator(); + while (glueIterator.hasNext()) { + Object glue = glueIterator.next(); + if (glue instanceof ScenarioScoped) { + ScenarioScoped scenarioScopedHookDefinition = (ScenarioScoped) glue; + scenarioScopedHookDefinition.disposeScenarioScope(); + glueIterator.remove(); + } + } + } + +} diff --git a/core/src/main/java/io/cucumber/core/runner/CoreStepDefinition.java b/core/src/main/java/io/cucumber/core/runner/CoreStepDefinition.java new file mode 100644 index 0000000000..bbf85dac29 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/runner/CoreStepDefinition.java @@ -0,0 +1,68 @@ +package io.cucumber.core.runner; + +import gherkin.pickles.PickleStep; +import io.cucumber.core.backend.ParameterInfo; +import io.cucumber.core.backend.StepDefinition; +import io.cucumber.core.stepexpression.Argument; +import io.cucumber.core.stepexpression.ArgumentMatcher; +import io.cucumber.core.stepexpression.StepExpression; +import io.cucumber.core.stepexpression.StepExpressionFactory; +import io.cucumber.core.stepexpression.TypeRegistry; +import io.cucumber.core.stepexpression.TypeResolver; + +import java.lang.reflect.Type; +import java.util.List; + +import static java.util.Objects.requireNonNull; + +class CoreStepDefinition { + + private final StepExpression expression; + private final ArgumentMatcher argumentMatcher; + private final StepDefinition stepDefinition; + private final Type[] types; + + CoreStepDefinition(StepDefinition stepDefinition, TypeRegistry typeRegistry) { + this.stepDefinition = requireNonNull(stepDefinition); + List parameterInfos = stepDefinition.parameterInfos(); + this.expression = createExpression(parameterInfos, stepDefinition.getPattern(), typeRegistry); + this.argumentMatcher = new ArgumentMatcher(this.expression); + this.types = getTypes(parameterInfos); + } + + private StepExpression createExpression(List parameterInfos, String expression, TypeRegistry typeRegistry) { + if (parameterInfos == null || parameterInfos.isEmpty()) { + return new StepExpressionFactory(typeRegistry).createExpression(expression); + } else { + ParameterInfo parameterInfo = parameterInfos.get(parameterInfos.size() - 1); + TypeResolver typeResolver = parameterInfo.getTypeResolver()::resolve; + boolean transposed = parameterInfo.isTransposed(); + return new StepExpressionFactory(typeRegistry).createExpression(expression, typeResolver, transposed); + } + } + + public String getPattern() { + return expression.getSource(); + } + + public StepDefinition getStepDefinition() { + return stepDefinition; + } + + List matchedArguments(PickleStep step) { + return argumentMatcher.argumentsFrom(step, types); + } + + private static Type[] getTypes(List parameterInfos) { + if (parameterInfos == null) { + return new Type[0]; + } + + Type[] types = new Type[parameterInfos.size()]; + for (int i = 0; i < types.length; i++) { + types[i] = parameterInfos.get(i).getType(); + } + return types; + } + +} diff --git a/core/src/main/java/cucumber/runtime/DefinitionArgument.java b/core/src/main/java/io/cucumber/core/runner/DefinitionArgument.java similarity index 69% rename from core/src/main/java/cucumber/runtime/DefinitionArgument.java rename to core/src/main/java/io/cucumber/core/runner/DefinitionArgument.java index b263a0f4a5..138063649e 100644 --- a/core/src/main/java/cucumber/runtime/DefinitionArgument.java +++ b/core/src/main/java/io/cucumber/core/runner/DefinitionArgument.java @@ -1,12 +1,12 @@ -package cucumber.runtime; +package io.cucumber.core.runner; -import cucumber.api.Argument; -import io.cucumber.stepexpression.ExpressionArgument; +import io.cucumber.core.event.Argument; +import io.cucumber.core.stepexpression.ExpressionArgument; import java.util.ArrayList; import java.util.List; -public final class DefinitionArgument implements Argument { +final class DefinitionArgument implements Argument { private final io.cucumber.cucumberexpressions.Group group; @@ -14,9 +14,9 @@ private DefinitionArgument(ExpressionArgument expressionArgument) { group = expressionArgument.getGroup(); } - public static List createArguments(List match) { + static List createArguments(List match) { List args = new ArrayList(); - for (io.cucumber.stepexpression.Argument argument : match) { + for (io.cucumber.core.stepexpression.Argument argument : match) { if (argument instanceof ExpressionArgument) { args.add(new DefinitionArgument((ExpressionArgument) argument)); } diff --git a/core/src/main/java/io/cucumber/core/runner/DuplicateDefaultDataTableCellTransformers.java b/core/src/main/java/io/cucumber/core/runner/DuplicateDefaultDataTableCellTransformers.java new file mode 100644 index 0000000000..26446a51e5 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/runner/DuplicateDefaultDataTableCellTransformers.java @@ -0,0 +1,20 @@ +package io.cucumber.core.runner; + +import io.cucumber.core.backend.DefaultDataTableCellTransformerDefinition; +import io.cucumber.core.exception.CucumberException; + +import java.util.List; + +import static java.util.stream.Collectors.joining; + +class DuplicateDefaultDataTableCellTransformers extends CucumberException { + DuplicateDefaultDataTableCellTransformers(List definitions) { + super(createMessage(definitions)); + } + + private static String createMessage(List definitions) { + return "There may not be more then one default table cell transformers. Found:" + definitions.stream() + .map(d -> d.getLocation(true)) + .collect(joining("\n - ", "\n - ", "\n")); + } +} diff --git a/core/src/main/java/io/cucumber/core/runner/DuplicateDefaultDataTableEntryTransformers.java b/core/src/main/java/io/cucumber/core/runner/DuplicateDefaultDataTableEntryTransformers.java new file mode 100644 index 0000000000..16ed120bee --- /dev/null +++ b/core/src/main/java/io/cucumber/core/runner/DuplicateDefaultDataTableEntryTransformers.java @@ -0,0 +1,20 @@ +package io.cucumber.core.runner; + +import io.cucumber.core.backend.DefaultDataTableEntryTransformerDefinition; +import io.cucumber.core.exception.CucumberException; + +import java.util.List; + +import static java.util.stream.Collectors.joining; + +class DuplicateDefaultDataTableEntryTransformers extends CucumberException { + DuplicateDefaultDataTableEntryTransformers(List definitions) { + super(createMessage(definitions)); + } + + private static String createMessage(List definitions) { + return "There may not be more then one default data table entry. Found:" + definitions.stream() + .map(d -> d.getLocation(true)) + .collect(joining("\n - ", "\n - ", "\n")); + } +} diff --git a/core/src/main/java/io/cucumber/core/runner/DuplicateDefaultParameterTransformers.java b/core/src/main/java/io/cucumber/core/runner/DuplicateDefaultParameterTransformers.java new file mode 100644 index 0000000000..b52f1313f7 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/runner/DuplicateDefaultParameterTransformers.java @@ -0,0 +1,20 @@ +package io.cucumber.core.runner; + +import io.cucumber.core.backend.DefaultParameterTransformerDefinition; +import io.cucumber.core.exception.CucumberException; + +import java.util.List; + +import static java.util.stream.Collectors.joining; + +class DuplicateDefaultParameterTransformers extends CucumberException { + DuplicateDefaultParameterTransformers(List definitions) { + super(createMessage(definitions)); + } + + private static String createMessage(List definitions) { + return "There may not be more then one default parameter transformer. Found:" + definitions.stream() + .map(d -> d.getLocation(true)) + .collect(joining("\n - ", "\n - ", "\n")); + } +} diff --git a/core/src/main/java/io/cucumber/core/runner/DuplicateStepDefinitionException.java b/core/src/main/java/io/cucumber/core/runner/DuplicateStepDefinitionException.java new file mode 100644 index 0000000000..bde5758872 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/runner/DuplicateStepDefinitionException.java @@ -0,0 +1,14 @@ +package io.cucumber.core.runner; + +import io.cucumber.core.backend.StepDefinition; +import io.cucumber.core.exception.CucumberException; + +final class DuplicateStepDefinitionException extends CucumberException { + DuplicateStepDefinitionException(StepDefinition a, StepDefinition b) { + super(createMessage(a, b)); + } + + private static String createMessage(StepDefinition a, StepDefinition b) { + return "Duplicate step definitions in " + a.getLocation(true) + " and " + b.getLocation(true); + } +} diff --git a/core/src/main/java/cucumber/runner/FailedPickleStepInstantiationMatch.java b/core/src/main/java/io/cucumber/core/runner/FailedPickleStepInstantiationMatch.java similarity index 76% rename from core/src/main/java/cucumber/runner/FailedPickleStepInstantiationMatch.java rename to core/src/main/java/io/cucumber/core/runner/FailedPickleStepInstantiationMatch.java index 209f2db3f2..06d39572da 100644 --- a/core/src/main/java/cucumber/runner/FailedPickleStepInstantiationMatch.java +++ b/core/src/main/java/io/cucumber/core/runner/FailedPickleStepInstantiationMatch.java @@ -1,8 +1,7 @@ -package cucumber.runner; +package io.cucumber.core.runner; -import cucumber.api.Scenario; import gherkin.pickles.PickleStep; -import io.cucumber.stepexpression.Argument; +import io.cucumber.core.api.Scenario; import java.util.Collections; @@ -10,7 +9,7 @@ final class FailedPickleStepInstantiationMatch extends PickleStepDefinitionMatch private final Throwable throwable; FailedPickleStepInstantiationMatch(String uri, PickleStep step, Throwable throwable) { - super(Collections.emptyList(), new NoStepDefinition(), uri, step); + super(Collections.emptyList(), new NoStepDefinition(), uri, step); this.throwable = removeFrameworkFramesAndAppendStepLocation(throwable, getStepLocation()); } diff --git a/core/src/main/java/cucumber/runner/HookDefinitionMatch.java b/core/src/main/java/io/cucumber/core/runner/HookDefinitionMatch.java similarity index 79% rename from core/src/main/java/cucumber/runner/HookDefinitionMatch.java rename to core/src/main/java/io/cucumber/core/runner/HookDefinitionMatch.java index 8d88f4c2da..2d0c9a6147 100644 --- a/core/src/main/java/cucumber/runner/HookDefinitionMatch.java +++ b/core/src/main/java/io/cucumber/core/runner/HookDefinitionMatch.java @@ -1,8 +1,7 @@ -package cucumber.runner; +package io.cucumber.core.runner; -import cucumber.api.Scenario; -import cucumber.runtime.HookDefinition; -import cucumber.runtime.StepDefinitionMatch; +import io.cucumber.core.api.Scenario; +import io.cucumber.core.backend.HookDefinition; final class HookDefinitionMatch implements StepDefinitionMatch { private final HookDefinition hookDefinition; diff --git a/core/src/main/java/cucumber/runner/HookTestStep.java b/core/src/main/java/io/cucumber/core/runner/HookTestStep.java similarity index 62% rename from core/src/main/java/cucumber/runner/HookTestStep.java rename to core/src/main/java/io/cucumber/core/runner/HookTestStep.java index b00fe3d9a2..b5aa510763 100644 --- a/core/src/main/java/cucumber/runner/HookTestStep.java +++ b/core/src/main/java/io/cucumber/core/runner/HookTestStep.java @@ -1,8 +1,8 @@ -package cucumber.runner; +package io.cucumber.core.runner; -import cucumber.api.HookType; +import io.cucumber.core.event.HookType; -final class HookTestStep extends TestStep implements cucumber.api.HookTestStep { +final class HookTestStep extends TestStep implements io.cucumber.core.event.HookTestStep { private final HookType hookType; HookTestStep(HookType hookType, HookDefinitionMatch definitionMatch) { diff --git a/core/src/main/java/cucumber/runner/Match.java b/core/src/main/java/io/cucumber/core/runner/Match.java similarity index 61% rename from core/src/main/java/cucumber/runner/Match.java rename to core/src/main/java/io/cucumber/core/runner/Match.java index e21daba2bd..c4e2e8e07b 100644 --- a/core/src/main/java/cucumber/runner/Match.java +++ b/core/src/main/java/io/cucumber/core/runner/Match.java @@ -1,17 +1,17 @@ -package cucumber.runner; +package io.cucumber.core.runner; -import io.cucumber.stepexpression.Argument; +import io.cucumber.core.stepexpression.Argument; import java.util.Collections; import java.util.List; import static java.util.Objects.requireNonNull; -class Match { +abstract class Match { private final List arguments; private final String location; - public static final Match UNDEFINED = new Match(Collections.emptyList(), null); + public static final Match UNDEFINED = new UndefinedMatch(); Match(List arguments, String location) { requireNonNull(arguments, "argument may not be null"); @@ -27,4 +27,9 @@ public String getLocation() { return location; } + private static final class UndefinedMatch extends Match { + UndefinedMatch() { + super(Collections.emptyList(), null); + } + } } diff --git a/core/src/main/java/cucumber/runner/NoStepDefinition.java b/core/src/main/java/io/cucumber/core/runner/NoStepDefinition.java similarity index 55% rename from core/src/main/java/cucumber/runner/NoStepDefinition.java rename to core/src/main/java/io/cucumber/core/runner/NoStepDefinition.java index fb9611a579..f085a076dc 100644 --- a/core/src/main/java/cucumber/runner/NoStepDefinition.java +++ b/core/src/main/java/io/cucumber/core/runner/NoStepDefinition.java @@ -1,28 +1,17 @@ -package cucumber.runner; +package io.cucumber.core.runner; -import cucumber.runtime.StepDefinition; -import io.cucumber.stepexpression.Argument; -import gherkin.pickles.PickleStep; +import io.cucumber.core.backend.ParameterInfo; +import io.cucumber.core.backend.StepDefinition; import java.util.List; final class NoStepDefinition implements StepDefinition { - @Override - public List matchedArguments(PickleStep step) { - return null; - } - @Override public String getLocation(boolean detail) { return null; } - @Override - public Integer getParameterCount() { - return 0; - } - @Override public void execute(Object[] args) { } @@ -33,13 +22,13 @@ public boolean isDefinedAt(StackTraceElement stackTraceElement) { } @Override - public String getPattern() { + public List parameterInfos() { return null; } @Override - public boolean isScenarioScoped() { - return false; + public String getPattern() { + return null; } } diff --git a/core/src/main/java/io/cucumber/core/runner/Options.java b/core/src/main/java/io/cucumber/core/runner/Options.java new file mode 100644 index 0000000000..83db587b25 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/runner/Options.java @@ -0,0 +1,17 @@ +package io.cucumber.core.runner; + +import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.snippets.SnippetType; + +import java.net.URI; +import java.util.List; + +public interface Options { + List getGlue(); + + boolean isDryRun(); + + SnippetType getSnippetType(); + + Class getObjectFactoryClass(); +} diff --git a/core/src/main/java/cucumber/runner/PickleStepDefinitionMatch.java b/core/src/main/java/io/cucumber/core/runner/PickleStepDefinitionMatch.java similarity index 89% rename from core/src/main/java/cucumber/runner/PickleStepDefinitionMatch.java rename to core/src/main/java/io/cucumber/core/runner/PickleStepDefinitionMatch.java index 876d2445d1..8a7a7a1da3 100644 --- a/core/src/main/java/cucumber/runner/PickleStepDefinitionMatch.java +++ b/core/src/main/java/io/cucumber/core/runner/PickleStepDefinitionMatch.java @@ -1,14 +1,14 @@ -package cucumber.runner; +package io.cucumber.core.runner; -import cucumber.api.Scenario; -import cucumber.runtime.CucumberException; -import cucumber.runtime.StepDefinition; -import cucumber.runtime.StepDefinitionMatch; +import io.cucumber.core.api.Scenario; +import io.cucumber.core.backend.ParameterInfo; +import io.cucumber.core.backend.StepDefinition; +import io.cucumber.core.exception.CucumberException; import gherkin.pickles.PickleStep; import io.cucumber.cucumberexpressions.CucumberExpressionException; import io.cucumber.datatable.CucumberDataTableException; import io.cucumber.datatable.UndefinedDataTableTypeException; -import io.cucumber.stepexpression.Argument; +import io.cucumber.core.stepexpression.Argument; import java.util.ArrayList; import java.util.List; @@ -20,7 +20,7 @@ class PickleStepDefinitionMatch extends Match implements StepDefinitionMatch { // to prevent it from ending up in the JSON. private final transient PickleStep step; - public PickleStepDefinitionMatch(List arguments, StepDefinition stepDefinition, String featurePath, PickleStep step) { + PickleStepDefinitionMatch(List arguments, StepDefinition stepDefinition, String featurePath, PickleStep step) { super(arguments, stepDefinition.getLocation(false)); this.stepDefinition = stepDefinition; this.featurePath = featurePath; @@ -31,9 +31,9 @@ public PickleStepDefinitionMatch(List arguments, StepDefinition stepDe public void runStep(Scenario scenario) throws Throwable { int argumentCount = getArguments().size(); - Integer parameterCount = stepDefinition.getParameterCount(); - if (parameterCount != null && argumentCount != parameterCount) { - throw arityMismatch(parameterCount); + List parameterInfos = stepDefinition.parameterInfos(); + if (parameterInfos != null && argumentCount != parameterInfos.size()) { + throw arityMismatch(parameterInfos.size()); } List result = new ArrayList<>(); try { diff --git a/core/src/main/java/cucumber/runner/PickleStepTestStep.java b/core/src/main/java/io/cucumber/core/runner/PickleStepTestStep.java similarity index 87% rename from core/src/main/java/cucumber/runner/PickleStepTestStep.java rename to core/src/main/java/io/cucumber/core/runner/PickleStepTestStep.java index 9781c43aa1..dc5e9bb357 100644 --- a/core/src/main/java/cucumber/runner/PickleStepTestStep.java +++ b/core/src/main/java/io/cucumber/core/runner/PickleStepTestStep.java @@ -1,13 +1,14 @@ -package cucumber.runner; +package io.cucumber.core.runner; -import cucumber.api.TestCase; -import cucumber.runtime.DefinitionArgument; +import io.cucumber.core.event.Argument; +import io.cucumber.core.event.TestCase; import gherkin.pickles.PickleStep; +import io.cucumber.core.eventbus.EventBus; import java.util.Collections; import java.util.List; -class PickleStepTestStep extends TestStep implements cucumber.api.PickleStepTestStep { +final class PickleStepTestStep extends TestStep implements io.cucumber.core.event.PickleStepTestStep { private final String uri; private final PickleStep step; private final List afterStepHookSteps; @@ -64,7 +65,7 @@ public PickleStep getPickleStep() { @Override public String getStepLocation() { - return uri + ":" + Integer.toString(getStepLine()); + return uri + ":" + getStepLine(); } @Override @@ -78,7 +79,7 @@ public String getStepText() { } @Override - public List getDefinitionArgument() { + public List getDefinitionArgument() { return DefinitionArgument.createArguments(definitionMatch.getArguments()); } diff --git a/core/src/main/java/io/cucumber/core/runner/Runner.java b/core/src/main/java/io/cucumber/core/runner/Runner.java new file mode 100644 index 0000000000..e95d01f647 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/runner/Runner.java @@ -0,0 +1,178 @@ +package io.cucumber.core.runner; + +import gherkin.events.PickleEvent; +import gherkin.pickles.PickleStep; +import gherkin.pickles.PickleTag; +import io.cucumber.core.api.TypeRegistryConfigurer; +import io.cucumber.core.backend.Backend; +import io.cucumber.core.backend.HookDefinition; +import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.event.HookType; +import io.cucumber.core.event.SnippetsSuggestedEvent; +import io.cucumber.core.eventbus.EventBus; +import io.cucumber.core.logging.Logger; +import io.cucumber.core.logging.LoggerFactory; +import io.cucumber.core.snippets.SnippetGenerator; +import io.cucumber.core.stepexpression.TypeRegistry; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; + +import static java.util.Collections.emptyList; + +public final class Runner { + + private static final Logger log = LoggerFactory.getLogger(Runner.class); + + private final CachingGlue glue; + private final EventBus bus; + private final Collection backends; + private final Options runnerOptions; + private final ObjectFactory objectFactory; + private final TypeRegistryConfigurer typeRegistryConfigurer; + private List snippetGenerators; + + public Runner(EventBus bus, Collection backends, ObjectFactory objectFactory, TypeRegistryConfigurer typeRegistryConfigurer, Options runnerOptions) { + this.bus = bus; + this.runnerOptions = runnerOptions; + this.backends = backends; + this.glue = new CachingGlue(bus); + this.objectFactory = objectFactory; + this.typeRegistryConfigurer = typeRegistryConfigurer; + List gluePaths = runnerOptions.getGlue(); + log.debug("Loading glue from " + gluePaths); + for (Backend backend : backends) { + log.debug("Loading glue for backend " + backend.getClass().getName()); + backend.loadGlue(this.glue, gluePaths); + } + } + + public EventBus getBus() { + return bus; + } + + public void runPickle(PickleEvent pickle) { + try { + TypeRegistry typeRegistry = createTypeRegistryForPickle(pickle); + snippetGenerators = createSnippetGeneratorsForPickle(typeRegistry); + + buildBackendWorlds(); // Java8 step definitions will be added to the glue here + + glue.prepareGlue(typeRegistry); + + TestCase testCase = createTestCaseForPickle(pickle); + testCase.run(bus); + } finally { + glue.removeScenarioScopedGlue(); + disposeBackendWorlds(); + } + } + + private List createSnippetGeneratorsForPickle(TypeRegistry typeRegistry) { + return backends.stream() + .map(Backend::getSnippet) + .map(s -> new SnippetGenerator(s, typeRegistry.parameterTypeRegistry())) + .collect(Collectors.toList()); + } + + private TypeRegistry createTypeRegistryForPickle(PickleEvent pickle) { + Locale locale = typeRegistryConfigurer.locale(); + if(locale == null){ + locale = new Locale(pickle.pickle.getLanguage()); + } + TypeRegistry typeRegistry = new TypeRegistry(locale); + typeRegistryConfigurer.configureTypeRegistry(typeRegistry); + return typeRegistry; + } + + private TestCase createTestCaseForPickle(PickleEvent pickleEvent) { + if (pickleEvent.pickle.getSteps().isEmpty()) { + return new TestCase(emptyList(), emptyList(), emptyList(), pickleEvent, runnerOptions.isDryRun()); + } + + List testSteps = createTestStepsForPickleSteps(pickleEvent); + List beforeHooks = createTestStepsForBeforeHooks(pickleEvent.pickle.getTags()); + List afterHooks = createTestStepsForAfterHooks(pickleEvent.pickle.getTags()); + return new TestCase(testSteps, beforeHooks, afterHooks, pickleEvent, runnerOptions.isDryRun()); + } + + private List createTestStepsForPickleSteps(PickleEvent pickleEvent) { + List testSteps = new ArrayList<>(); + + for (PickleStep step : pickleEvent.pickle.getSteps()) { + PickleStepDefinitionMatch match; + try { + match = glue.stepDefinitionMatch(pickleEvent.uri, step); + if (match == null) { + List snippets = new ArrayList<>(); + for (SnippetGenerator snippetGenerator : snippetGenerators) { + List snippet = snippetGenerator.getSnippet(step, "**KEYWORD**", runnerOptions.getSnippetType()); + snippets.addAll(snippet); + } + if (!snippets.isEmpty()) { + bus.send(new SnippetsSuggestedEvent(bus.getInstant(), pickleEvent.uri, locations(step), snippets)); + } + match = new UndefinedPickleStepDefinitionMatch(step); + } + } catch (AmbiguousStepDefinitionsException e) { + match = new AmbiguousPickleStepDefinitionsMatch(pickleEvent.uri, step, e); + } catch (Throwable t) { + match = new FailedPickleStepInstantiationMatch(pickleEvent.uri, step, t); + } + + + List afterStepHookSteps = createAfterStepHooks(pickleEvent.pickle.getTags()); + List beforeStepHookSteps = createBeforeStepHooks(pickleEvent.pickle.getTags()); + testSteps.add(new PickleStepTestStep(pickleEvent.uri, step, beforeStepHookSteps, afterStepHookSteps, match)); + } + + return testSteps; + } + + private List locations(PickleStep step) { + return step.getLocations().stream() + .map(p -> new SnippetsSuggestedEvent.Location(p.getLine(), p.getLine())) + .collect(Collectors.toList()); + } + + private List createTestStepsForBeforeHooks(List tags) { + return createTestStepsForHooks(tags, glue.getBeforeHooks(), HookType.BEFORE); + } + + private List createTestStepsForAfterHooks(List tags) { + return createTestStepsForHooks(tags, glue.getAfterHooks(), HookType.AFTER); + } + + private List createTestStepsForHooks(List tags, Collection hooks, HookType hookType) { + return hooks.stream() + .filter(hook -> hook.matches(tags)) + .map(hook -> new HookTestStep(hookType, new HookDefinitionMatch(hook))) + .collect(Collectors.toList()); + } + + private List createAfterStepHooks(List tags) { + return createTestStepsForHooks(tags, glue.getAfterStepHooks(), HookType.AFTER_STEP); + } + + private List createBeforeStepHooks(List tags) { + return createTestStepsForHooks(tags, glue.getBeforeStepHooks(), HookType.BEFORE_STEP); + } + + private void buildBackendWorlds() { + objectFactory.start(); + for (Backend backend : backends) { + backend.buildWorld(); + } + } + + private void disposeBackendWorlds() { + for (Backend backend : backends) { + backend.disposeWorld(); + } + objectFactory.stop(); + } +} diff --git a/core/src/main/java/io/cucumber/core/runner/Scenario.java b/core/src/main/java/io/cucumber/core/runner/Scenario.java new file mode 100644 index 0000000000..44a5edf2cd --- /dev/null +++ b/core/src/main/java/io/cucumber/core/runner/Scenario.java @@ -0,0 +1,91 @@ +package io.cucumber.core.runner; + +import io.cucumber.core.event.TestCase; +import io.cucumber.core.event.*; +import io.cucumber.core.eventbus.EventBus; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import static java.util.Collections.max; +import static java.util.Comparator.comparing; +import static java.util.Objects.requireNonNull; + +class Scenario implements io.cucumber.core.api.Scenario { + + private final List stepResults = new ArrayList<>(); + private final EventBus bus; + private final TestCase testCase; + + Scenario(EventBus bus, io.cucumber.core.event.TestCase testCase) { + this.bus = requireNonNull(bus); + this.testCase = requireNonNull(testCase); + } + + void add(Result result) { + stepResults.add(result); + } + + @Override + public Collection getSourceTagNames() { + return testCase.getTags(); + } + + @Override + public Status getStatus() { + if (stepResults.isEmpty()) { + return Status.UNDEFINED; + } + + return max(stepResults, comparing(Result::getStatus)).getStatus(); + } + + @Override + public boolean isFailed() { + return getStatus() == Status.FAILED; + } + + @Override + public void embed(byte[] data, String mimeType) { + bus.send(new EmbedEvent(bus.getInstant(), testCase, data, mimeType)); + } + + @Override + public void embed(byte[] data, String mimeType, String name) { + bus.send(new EmbedEvent(bus.getInstant(), testCase, data, mimeType, name)); + } + + @Override + public void write(String text) { + bus.send(new WriteEvent(bus.getInstant(), testCase, text)); + } + + @Override + public String getName() { + return testCase.getName(); + } + + @Override + public String getId() { + return testCase.getUri() + ":" + getLine(); + } + + @Override + public String getUri() { + return testCase.getUri(); + } + + @Override + public Integer getLine() { + return testCase.getLine(); + } + + Throwable getError() { + if (stepResults.isEmpty()) { + return null; + } + + return max(stepResults, comparing(Result::getStatus)).getError(); + } +} diff --git a/core/src/main/java/cucumber/runtime/ScenarioScoped.java b/core/src/main/java/io/cucumber/core/runner/ScenarioScoped.java similarity index 83% rename from core/src/main/java/cucumber/runtime/ScenarioScoped.java rename to core/src/main/java/io/cucumber/core/runner/ScenarioScoped.java index fd8b80e0ab..f0c27adcef 100644 --- a/core/src/main/java/cucumber/runtime/ScenarioScoped.java +++ b/core/src/main/java/io/cucumber/core/runner/ScenarioScoped.java @@ -1,4 +1,4 @@ -package cucumber.runtime; +package io.cucumber.core.runner; public interface ScenarioScoped { /** diff --git a/core/src/main/java/cucumber/runtime/StepDefinitionMatch.java b/core/src/main/java/io/cucumber/core/runner/StepDefinitionMatch.java similarity index 58% rename from core/src/main/java/cucumber/runtime/StepDefinitionMatch.java rename to core/src/main/java/io/cucumber/core/runner/StepDefinitionMatch.java index 30435a9e74..a2513c8c52 100644 --- a/core/src/main/java/cucumber/runtime/StepDefinitionMatch.java +++ b/core/src/main/java/io/cucumber/core/runner/StepDefinitionMatch.java @@ -1,8 +1,8 @@ -package cucumber.runtime; +package io.cucumber.core.runner; -import cucumber.api.Scenario; +import io.cucumber.core.api.Scenario; -public interface StepDefinitionMatch { +interface StepDefinitionMatch { void runStep(Scenario scenario) throws Throwable; void dryRunStep(Scenario scenario) throws Throwable; diff --git a/core/src/main/java/cucumber/runner/TestCase.java b/core/src/main/java/io/cucumber/core/runner/TestCase.java similarity index 59% rename from core/src/main/java/cucumber/runner/TestCase.java rename to core/src/main/java/io/cucumber/core/runner/TestCase.java index fe73cf66e1..85c6926a07 100644 --- a/core/src/main/java/cucumber/runner/TestCase.java +++ b/core/src/main/java/io/cucumber/core/runner/TestCase.java @@ -1,25 +1,28 @@ -package cucumber.runner; +package io.cucumber.core.runner; -import cucumber.api.Result; -import cucumber.api.TestStep; -import cucumber.api.event.TestCaseFinished; -import cucumber.api.event.TestCaseStarted; +import io.cucumber.core.event.Result; +import io.cucumber.core.event.TestStep; +import io.cucumber.core.event.TestCaseFinished; +import io.cucumber.core.event.TestCaseStarted; import gherkin.events.PickleEvent; -import gherkin.pickles.PickleLocation; import gherkin.pickles.PickleTag; +import io.cucumber.core.eventbus.EventBus; import java.net.URI; +import java.time.Duration; +import java.time.Instant; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; -final class TestCase implements cucumber.api.TestCase { +final class TestCase implements io.cucumber.core.event.TestCase { private final PickleEvent pickleEvent; private final List testSteps; private final boolean dryRun; private final List beforeHooks; private final List afterHooks; - public TestCase(List testSteps, + TestCase(List testSteps, List beforeHooks, List afterHooks, PickleEvent pickleEvent, @@ -33,9 +36,8 @@ public TestCase(List testSteps, void run(EventBus bus) { boolean skipNextStep = this.dryRun; - Long startTimeMillis = bus.getTimeMillis(); - Long startTimeNanos = bus.getTime(); - bus.send(new TestCaseStarted(startTimeNanos, startTimeMillis, this)); + Instant startTimeInstant = bus.getInstant(); + bus.send(new TestCaseStarted(startTimeInstant, this)); Scenario scenario = new Scenario(bus, this); for (HookTestStep before : beforeHooks) { @@ -50,14 +52,13 @@ void run(EventBus bus) { after.run(this, bus, scenario, dryRun); } - Long stopTimeNanos = bus.getTime(); - Long stopTimeMillis = bus.getTimeMillis(); - bus.send(new TestCaseFinished(stopTimeNanos, stopTimeMillis, this, new Result(scenario.getStatus(), stopTimeNanos - startTimeNanos, scenario.getError()))); + Instant stopTimeInstant = bus.getInstant(); + bus.send(new TestCaseFinished(stopTimeInstant, this, new Result(scenario.getStatus(), Duration.between(startTimeInstant, stopTimeInstant), scenario.getError()))); } @Override public List getTestSteps() { - List testSteps = new ArrayList(beforeHooks); + List testSteps = new ArrayList<>(beforeHooks); for (PickleStepTestStep step : this.testSteps) { testSteps.addAll(step.getBeforeStepHookSteps()); testSteps.add(step); @@ -74,7 +75,7 @@ public String getName() { @Override public String getScenarioDesignation() { - return fileColonLine(pickleEvent.pickle.getLocations().get(0)) + " # " + getName(); + return fileColonLine(getLine()) + " # " + getName(); } @Override @@ -82,25 +83,19 @@ public String getUri() { return pickleEvent.uri; } - @Override - public int getLine() { + public Integer getLine() { return pickleEvent.pickle.getLocations().get(0).getLine(); } - public List getLines() { - List lines = new ArrayList<>(); - for (PickleLocation location : pickleEvent.pickle.getLocations()) { - lines.add(location.getLine()); - } - return lines; - } - - private String fileColonLine(PickleLocation location) { - return URI.create(pickleEvent.uri).getSchemeSpecificPart() + ":" + location.getLine(); + private String fileColonLine(Integer line) { + return URI.create(pickleEvent.uri).getSchemeSpecificPart() + ":" + line; } @Override - public List getTags() { - return pickleEvent.pickle.getTags(); + public List getTags() { + return pickleEvent.pickle.getTags() + .stream() + .map(PickleTag::getName) + .collect(Collectors.toList()); } } diff --git a/core/src/main/java/cucumber/runner/TestStep.java b/core/src/main/java/io/cucumber/core/runner/TestStep.java similarity index 53% rename from core/src/main/java/cucumber/runner/TestStep.java rename to core/src/main/java/io/cucumber/core/runner/TestStep.java index cdc8c1e374..72aa001898 100644 --- a/core/src/main/java/cucumber/runner/TestStep.java +++ b/core/src/main/java/io/cucumber/core/runner/TestStep.java @@ -1,15 +1,20 @@ -package cucumber.runner; +package io.cucumber.core.runner; -import cucumber.api.Pending; -import cucumber.api.Result; -import cucumber.api.TestCase; -import cucumber.api.event.TestStepFinished; -import cucumber.api.event.TestStepStarted; -import cucumber.runtime.StepDefinitionMatch; +import io.cucumber.core.backend.Pending; +import io.cucumber.core.event.Result; +import io.cucumber.core.event.Status; +import io.cucumber.core.event.TestCase; +import io.cucumber.core.event.TestStepFinished; +import io.cucumber.core.event.TestStepStarted; +import io.cucumber.core.eventbus.EventBus; +import java.time.Duration; +import java.time.Instant; import java.util.Arrays; -abstract class TestStep implements cucumber.api.TestStep { +import static java.time.Duration.ZERO; + +abstract class TestStep implements io.cucumber.core.event.TestStep { private static final String[] ASSUMPTION_VIOLATED_EXCEPTIONS = { "org.junit.AssumptionViolatedException", "org.junit.internal.AssumptionViolatedException", @@ -41,10 +46,9 @@ public String getCodeLocation() { * @return true iff subsequent skippable steps should be skipped */ boolean run(TestCase testCase, EventBus bus, Scenario scenario, boolean skipSteps) { - Long startTimeMillis = bus.getTimeMillis(); - Long startTimeNanos = bus.getTime(); - bus.send(new TestStepStarted(startTimeNanos, startTimeMillis, testCase, this)); - Result.Type status; + Instant startTimeMillis = bus.getInstant(); + bus.send(new TestStepStarted(startTimeMillis, testCase, this)); + Status status; Throwable error = null; try { status = executeStep(scenario, skipSteps); @@ -52,43 +56,42 @@ boolean run(TestCase testCase, EventBus bus, Scenario scenario, boolean skipStep error = t; status = mapThrowableToStatus(t); } - Long stopTimeNanos = bus.getTime(); - Long stopTimeMillis = bus.getTimeMillis(); - Result result = mapStatusToResult(status, error, stopTimeNanos - startTimeNanos); + Instant stopTimeNanos = bus.getInstant(); + Result result = mapStatusToResult(status, error, Duration.between(startTimeMillis, stopTimeNanos)); scenario.add(result); - bus.send(new TestStepFinished(stopTimeNanos, stopTimeMillis, testCase, this, result)); - return !result.is(Result.Type.PASSED); + bus.send(new TestStepFinished(stopTimeNanos, testCase, this, result)); + return !result.getStatus().is(Status.PASSED); } - private Result.Type executeStep(Scenario scenario, boolean skipSteps) throws Throwable { + private Status executeStep(Scenario scenario, boolean skipSteps) throws Throwable { if (!skipSteps) { stepDefinitionMatch.runStep(scenario); - return Result.Type.PASSED; + return Status.PASSED; } else { stepDefinitionMatch.dryRunStep(scenario); - return Result.Type.SKIPPED; + return Status.SKIPPED; } } - private Result.Type mapThrowableToStatus(Throwable t) { + private Status mapThrowableToStatus(Throwable t) { if (t.getClass().isAnnotationPresent(Pending.class)) { - return Result.Type.PENDING; + return Status.PENDING; } if (Arrays.binarySearch(ASSUMPTION_VIOLATED_EXCEPTIONS, t.getClass().getName()) >= 0) { - return Result.Type.SKIPPED; + return Status.SKIPPED; } if (t.getClass() == UndefinedStepDefinitionException.class) { - return Result.Type.UNDEFINED; + return Status.UNDEFINED; } if (t.getClass() == AmbiguousStepDefinitionsException.class) { - return Result.Type.AMBIGUOUS; + return Status.AMBIGUOUS; } - return Result.Type.FAILED; + return Status.FAILED; } - private Result mapStatusToResult(Result.Type status, Throwable error, long duration) { - if (status == Result.Type.UNDEFINED) { - return Result.UNDEFINED; + private Result mapStatusToResult(Status status, Throwable error, Duration duration) { + if (status == Status.UNDEFINED) { + return new Result(status, ZERO, null); } return new Result(status, duration, error); } diff --git a/core/src/main/java/cucumber/runner/UndefinedPickleStepDefinitionMatch.java b/core/src/main/java/io/cucumber/core/runner/UndefinedPickleStepDefinitionMatch.java similarity index 69% rename from core/src/main/java/cucumber/runner/UndefinedPickleStepDefinitionMatch.java rename to core/src/main/java/io/cucumber/core/runner/UndefinedPickleStepDefinitionMatch.java index 90f25406aa..bd52292f34 100644 --- a/core/src/main/java/cucumber/runner/UndefinedPickleStepDefinitionMatch.java +++ b/core/src/main/java/io/cucumber/core/runner/UndefinedPickleStepDefinitionMatch.java @@ -1,12 +1,12 @@ -package cucumber.runner; +package io.cucumber.core.runner; -import io.cucumber.stepexpression.Argument; -import cucumber.api.Scenario; +import io.cucumber.core.stepexpression.Argument; +import io.cucumber.core.api.Scenario; import gherkin.pickles.PickleStep; import java.util.Collections; -class UndefinedPickleStepDefinitionMatch extends PickleStepDefinitionMatch { +final class UndefinedPickleStepDefinitionMatch extends PickleStepDefinitionMatch { UndefinedPickleStepDefinitionMatch(PickleStep step) { super(Collections.emptyList(), new NoStepDefinition(), null, step); diff --git a/core/src/main/java/cucumber/runner/UndefinedStepDefinitionException.java b/core/src/main/java/io/cucumber/core/runner/UndefinedStepDefinitionException.java similarity index 66% rename from core/src/main/java/cucumber/runner/UndefinedStepDefinitionException.java rename to core/src/main/java/io/cucumber/core/runner/UndefinedStepDefinitionException.java index 944e325fa5..832ea10dbe 100644 --- a/core/src/main/java/cucumber/runner/UndefinedStepDefinitionException.java +++ b/core/src/main/java/io/cucumber/core/runner/UndefinedStepDefinitionException.java @@ -1,6 +1,6 @@ -package cucumber.runner; +package io.cucumber.core.runner; -import cucumber.runtime.CucumberException; +import io.cucumber.core.exception.CucumberException; final class UndefinedStepDefinitionException extends CucumberException { diff --git a/core/src/main/java/io/cucumber/core/runtime/BackendServiceLoader.java b/core/src/main/java/io/cucumber/core/runtime/BackendServiceLoader.java new file mode 100644 index 0000000000..9404c4172e --- /dev/null +++ b/core/src/main/java/io/cucumber/core/runtime/BackendServiceLoader.java @@ -0,0 +1,51 @@ +package io.cucumber.core.runtime; + +import io.cucumber.core.backend.Backend; +import io.cucumber.core.backend.BackendProviderService; +import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.exception.CucumberException; +import io.cucumber.core.io.ResourceLoader; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.ServiceLoader; + +/** + * Supplies instances of {@link Backend} created by using a {@link ServiceLoader} + * to locate instance of {@link BackendSupplier}. + */ +public final class BackendServiceLoader implements BackendSupplier { + + private final ResourceLoader resourceLoader; + private final ObjectFactorySupplier objectFactorySupplier; + + public BackendServiceLoader(ResourceLoader resourceLoader, ObjectFactorySupplier objectFactorySupplier) { + this.resourceLoader = resourceLoader; + this.objectFactorySupplier = objectFactorySupplier; + } + + @Override + public Collection get() { + return get(ServiceLoader.load(BackendProviderService.class)); + } + + Collection get(Iterable serviceLoader) { + Collection backends = loadBackends(serviceLoader); + if (backends.isEmpty()) { + throw new CucumberException("No backends were found. Please make sure you have a backend module on your CLASSPATH."); + } + return backends; + } + + private Collection loadBackends(Iterable serviceLoader) { + List backends = new ArrayList<>(); + for (BackendProviderService backendProviderService : serviceLoader) { + ObjectFactory objectFactory = objectFactorySupplier.get(); + backends.add(backendProviderService.create(objectFactory, objectFactory, resourceLoader)); + } + return backends; + } + + +} diff --git a/core/src/main/java/cucumber/runtime/BackendSupplier.java b/core/src/main/java/io/cucumber/core/runtime/BackendSupplier.java similarity index 58% rename from core/src/main/java/cucumber/runtime/BackendSupplier.java rename to core/src/main/java/io/cucumber/core/runtime/BackendSupplier.java index 59506410ef..d6f5cd0c43 100644 --- a/core/src/main/java/cucumber/runtime/BackendSupplier.java +++ b/core/src/main/java/io/cucumber/core/runtime/BackendSupplier.java @@ -1,4 +1,6 @@ -package cucumber.runtime; +package io.cucumber.core.runtime; + +import io.cucumber.core.backend.Backend; import java.util.Collection; diff --git a/core/src/main/java/cucumber/runtime/FeaturePathFeatureSupplier.java b/core/src/main/java/io/cucumber/core/runtime/FeaturePathFeatureSupplier.java similarity index 62% rename from core/src/main/java/cucumber/runtime/FeaturePathFeatureSupplier.java rename to core/src/main/java/io/cucumber/core/runtime/FeaturePathFeatureSupplier.java index 63460904c7..cdab55f17d 100644 --- a/core/src/main/java/cucumber/runtime/FeaturePathFeatureSupplier.java +++ b/core/src/main/java/io/cucumber/core/runtime/FeaturePathFeatureSupplier.java @@ -1,26 +1,27 @@ -package cucumber.runtime; +package io.cucumber.core.runtime; -import cucumber.runtime.model.CucumberFeature; -import cucumber.runtime.model.FeatureLoader; -import cucumber.util.FixJava; import io.cucumber.core.logging.Logger; import io.cucumber.core.logging.LoggerFactory; -import io.cucumber.core.options.FeatureOptions; +import io.cucumber.core.feature.CucumberFeature; +import io.cucumber.core.feature.FeatureLoader; +import io.cucumber.core.feature.Options; import java.net.URI; import java.util.List; +import static java.util.stream.Collectors.joining; + /** * Supplies a list of features found on the the feature path provided to RuntimeOptions. */ -public class FeaturePathFeatureSupplier implements FeatureSupplier { +public final class FeaturePathFeatureSupplier implements FeatureSupplier { private static final Logger log = LoggerFactory.getLogger(FeaturePathFeatureSupplier.class); private final FeatureLoader featureLoader; - private final FeatureOptions featureOptions; + private final Options featureOptions; - public FeaturePathFeatureSupplier(FeatureLoader featureLoader, FeatureOptions featureOptions) { + public FeaturePathFeatureSupplier(FeatureLoader featureLoader, Options featureOptions) { this.featureLoader = featureLoader; this.featureOptions = featureOptions; } @@ -29,14 +30,14 @@ public FeaturePathFeatureSupplier(FeatureLoader featureLoader, FeatureOptions fe public List get() { List featurePaths = featureOptions.getFeaturePaths(); - log.debug("Loading features from " + FixJava.join(featurePaths, ", ")); + log.debug("Loading features from " + featurePaths.stream().map(URI::toString).collect(joining(", "))); List cucumberFeatures = featureLoader.load(featurePaths); if (cucumberFeatures.isEmpty()) { if (featurePaths.isEmpty()) { log.warn("Got no path to feature directory or feature file"); } else { - log.warn("No features found at " + FixJava.join(featurePaths, ", ")); + log.warn("No features found at " + featurePaths.stream().map(URI::toString).collect(joining(", "))); } } diff --git a/core/src/main/java/cucumber/runtime/FeatureSupplier.java b/core/src/main/java/io/cucumber/core/runtime/FeatureSupplier.java similarity index 53% rename from core/src/main/java/cucumber/runtime/FeatureSupplier.java rename to core/src/main/java/io/cucumber/core/runtime/FeatureSupplier.java index e758495854..4b1cc58fcd 100644 --- a/core/src/main/java/cucumber/runtime/FeatureSupplier.java +++ b/core/src/main/java/io/cucumber/core/runtime/FeatureSupplier.java @@ -1,6 +1,6 @@ -package cucumber.runtime; +package io.cucumber.core.runtime; -import cucumber.runtime.model.CucumberFeature; +import io.cucumber.core.feature.CucumberFeature; import java.util.List; diff --git a/core/src/main/java/io/cucumber/core/runtime/Invoker.java b/core/src/main/java/io/cucumber/core/runtime/Invoker.java new file mode 100644 index 0000000000..8fb4c5500f --- /dev/null +++ b/core/src/main/java/io/cucumber/core/runtime/Invoker.java @@ -0,0 +1,123 @@ +package io.cucumber.core.runtime; + +import io.cucumber.core.exception.CucumberException; +import io.cucumber.core.reflection.MethodFormat; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +public final class Invoker { + + private Invoker() { + + } + + public static T timeout(Callback callback, long timeoutMillis) throws Throwable { + if (timeoutMillis == 0) { + return callback.call(); + } + + /* We need to ensure a happens before relation exists between these events; + * a. the timer setting the interrupt flag on the execution thread. + * b. terminating and cleaning up the timer + * To do this we synchronize on monitor. The atomic boolean is merely a convenient container. + */ + final Thread executionThread = Thread.currentThread(); + final Object monitor = new Object(); + final AtomicBoolean done = new AtomicBoolean(); + + ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); + ScheduledFuture timer = executorService.schedule(new Runnable() { + @Override + public void run() { + synchronized (monitor) { + if (!done.get()) { + executionThread.interrupt(); + } + } + } + }, timeoutMillis, TimeUnit.MILLISECONDS); + + try { + T result = callback.call(); + // The callback may have been busy waiting. + if (Thread.interrupted()) { + throw new TimeoutException("Timed out after " + timeoutMillis + "ms."); + } + return result; + } catch (InterruptedException timeout) { + throw new TimeoutException("Timed out after " + timeoutMillis + "ms."); + } finally { + synchronized (monitor) { + done.set(true); + timer.cancel(true); + executorService.shutdownNow(); + // Clear the interrupted flag. It may have been set by the timer just before we returned the result. + Thread.interrupted(); + } + } + } + + public static Object invoke(final Object target, final Method method, long timeoutMillis, final Object... args) throws Throwable { + final Method targetMethod = targetMethod(target, method); + return timeout(new Callback() { + @Override + public Object call() throws Throwable { + boolean accessible = targetMethod.isAccessible(); + try { + targetMethod.setAccessible(true); + return targetMethod.invoke(target, args); + } catch (IllegalArgumentException e) { + throw new CucumberException("Failed to invoke " + MethodFormat.FULL.format(targetMethod) + + ", caused by " + e.getClass().getName() + ": " + e.getMessage(), e); + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } catch (IllegalAccessException e) { + throw new CucumberException("Failed to invoke " + MethodFormat.FULL.format(targetMethod) + + ", caused by " + e.getClass().getName() + ": " + e.getMessage(), e); + } finally { + targetMethod.setAccessible(accessible); + } + } + }, timeoutMillis); + } + + private static Method targetMethod(final Object target, final Method method) throws NoSuchMethodException { + final Class targetClass = target.getClass(); + final Class declaringClass = method.getDeclaringClass(); + + // Immediately return the provided method if the class loaders are the same. + if (targetClass.getClassLoader().equals(declaringClass.getClassLoader())) { + return method; + } else { + // Check if the method is publicly accessible. Note that methods from interfaces are always public. + if (Modifier.isPublic(method.getModifiers())) { + return targetClass.getMethod(method.getName(), method.getParameterTypes()); + } + + // Loop through all the super classes until the declared method is found. + Class currentClass = targetClass; + while (currentClass != Object.class) { + try { + return currentClass.getDeclaredMethod(method.getName(), method.getParameterTypes()); + } catch (NoSuchMethodException e) { + currentClass = currentClass.getSuperclass(); + } + } + + // The method does not exist in the class hierarchy. + throw new NoSuchMethodException(String.valueOf(method)); + } + } + + public interface Callback { + T call() throws Throwable; + } +} diff --git a/core/src/main/java/io/cucumber/core/runtime/ObjectFactorySupplier.java b/core/src/main/java/io/cucumber/core/runtime/ObjectFactorySupplier.java new file mode 100644 index 0000000000..607712fd65 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/runtime/ObjectFactorySupplier.java @@ -0,0 +1,9 @@ +package io.cucumber.core.runtime; + +import io.cucumber.core.backend.ObjectFactory; + +public interface ObjectFactorySupplier { + + ObjectFactory get(); + +} diff --git a/core/src/main/java/io/cucumber/core/runtime/RunnerSupplier.java b/core/src/main/java/io/cucumber/core/runtime/RunnerSupplier.java new file mode 100644 index 0000000000..df4dbb69e3 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/runtime/RunnerSupplier.java @@ -0,0 +1,7 @@ +package io.cucumber.core.runtime; + +import io.cucumber.core.runner.Runner; + +public interface RunnerSupplier { + Runner get(); +} diff --git a/core/src/main/java/cucumber/runtime/Runtime.java b/core/src/main/java/io/cucumber/core/runtime/Runtime.java similarity index 55% rename from core/src/main/java/cucumber/runtime/Runtime.java rename to core/src/main/java/io/cucumber/core/runtime/Runtime.java index 46f6e54a69..ea3ab04d9b 100644 --- a/core/src/main/java/cucumber/runtime/Runtime.java +++ b/core/src/main/java/io/cucumber/core/runtime/Runtime.java @@ -1,29 +1,35 @@ -package cucumber.runtime; - -import cucumber.api.Plugin; -import cucumber.api.StepDefinitionReporter; -import cucumber.api.event.TestRunFinished; -import cucumber.api.event.TestRunStarted; -import cucumber.runner.EventBus; -import cucumber.runner.RunnerSupplier; -import cucumber.runner.SingletonRunnerSupplier; -import cucumber.runner.ThreadLocalRunnerSupplier; -import cucumber.runner.TimeService; -import cucumber.runner.TimeServiceEventBus; -import cucumber.runtime.filter.Filters; -import cucumber.runtime.formatter.PluginFactory; -import cucumber.runtime.formatter.Plugins; -import cucumber.runtime.io.MultiLoader; -import cucumber.runtime.io.ResourceLoader; -import cucumber.runtime.io.ResourceLoaderClassFinder; -import cucumber.runtime.model.CucumberFeature; -import cucumber.runtime.model.FeatureLoader; -import cucumber.runtime.order.PickleOrder; +package io.cucumber.core.runtime; + import gherkin.events.PickleEvent; +import io.cucumber.core.backend.ObjectFactoryServiceLoader; +import io.cucumber.core.event.EventHandler; +import io.cucumber.core.event.EventPublisher; +import io.cucumber.core.event.Result; +import io.cucumber.core.event.Status; +import io.cucumber.core.event.TestCaseFinished; +import io.cucumber.core.event.TestRunFinished; +import io.cucumber.core.event.TestRunStarted; +import io.cucumber.core.event.TestSourceRead; +import io.cucumber.core.eventbus.EventBus; +import io.cucumber.core.exception.CompositeCucumberException; +import io.cucumber.core.exception.CucumberException; +import io.cucumber.core.feature.CucumberFeature; +import io.cucumber.core.feature.FeatureLoader; +import io.cucumber.core.filter.Filters; +import io.cucumber.core.io.ClassFinder; +import io.cucumber.core.io.MultiLoader; +import io.cucumber.core.io.ResourceLoader; +import io.cucumber.core.io.ResourceLoaderClassFinder; import io.cucumber.core.logging.Logger; import io.cucumber.core.logging.LoggerFactory; import io.cucumber.core.options.RuntimeOptions; +import io.cucumber.core.order.PickleOrder; +import io.cucumber.core.plugin.ConcurrentEventListener; +import io.cucumber.core.plugin.Plugin; +import io.cucumber.core.plugin.PluginFactory; +import io.cucumber.core.plugin.Plugins; +import java.time.Clock; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -37,10 +43,15 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import static java.util.Collections.emptyList; +import static java.util.Collections.max; +import static java.util.Collections.min; +import static java.util.Comparator.comparing; + /** * This is the main entry point for running Cucumber features from the CLI. */ -public class Runtime { +public final class Runtime { private static final Logger log = LoggerFactory.getLogger(Runtime.class); @@ -50,55 +61,47 @@ public class Runtime { private final Filters filters; private final EventBus bus; private final FeatureSupplier featureSupplier; - private final Plugins plugins; private final ExecutorService executor; private final PickleOrder pickleOrder; - public Runtime(final Plugins plugins, - final RuntimeOptions runtimeOptions, - final EventBus bus, - final Filters filters, - final RunnerSupplier runnerSupplier, - final FeatureSupplier featureSupplier, - final ExecutorService executor, - final PickleOrder pickleOrder) { - - this.plugins = plugins; + private Runtime(final ExitStatus exitStatus, + final EventBus bus, + final Filters filters, + final RunnerSupplier runnerSupplier, + final FeatureSupplier featureSupplier, + final ExecutorService executor, + final PickleOrder pickleOrder) { this.filters = filters; this.bus = bus; this.runnerSupplier = runnerSupplier; this.featureSupplier = featureSupplier; this.executor = executor; - this.exitStatus = new ExitStatus(runtimeOptions); + this.exitStatus = exitStatus; this.pickleOrder = pickleOrder; - exitStatus.setEventPublisher(bus); } public void run() { final List features = featureSupplier.get(); - bus.send(new TestRunStarted(bus.getTime(), bus.getTimeMillis())); + bus.send(new TestRunStarted(bus.getInstant())); for (CucumberFeature feature : features) { - feature.sendTestSourceRead(bus); + bus.send(new TestSourceRead(bus.getInstant(), feature.getUri().toString(), feature.getSource())); } - final StepDefinitionReporter stepDefinitionReporter = plugins.stepDefinitionReporter(); - runnerSupplier.get().reportStepDefinitions(stepDefinitionReporter); - final List filteredEvents = new ArrayList<>(); for (CucumberFeature feature : features) { for (final PickleEvent pickleEvent : feature.getPickles()) { if (filters.matchesFilters(pickleEvent)) { - filteredEvents.add(pickleEvent); + filteredEvents.add(pickleEvent); } } } - + final List orderedEvents = pickleOrder.orderPickleEvents(filteredEvents); final List limitedEvents = filters.limitPickleEvents(orderedEvents); - final List> executingPickles = new ArrayList<>(); - for(final PickleEvent pickleEvent : limitedEvents) { - executingPickles.add(executor.submit(new Runnable() { + final List> executingPickles = new ArrayList<>(); + for (final PickleEvent pickleEvent : limitedEvents) { + executingPickles.add(executor.submit(new Runnable() { @Override public void run() { runnerSupplier.get().runPickle(pickleEvent); @@ -112,21 +115,21 @@ public void run() { for (Future executingPickle : executingPickles) { try { executingPickle.get(); - } catch (ExecutionException e){ + } catch (ExecutionException e) { log.error("Exception while executing pickle", e); thrown.add(e.getCause()); - } catch (InterruptedException e){ + } catch (InterruptedException e) { executor.shutdownNow(); throw new CucumberException(e); } } - if(thrown.size() == 1){ + if (thrown.size() == 1) { throw new CucumberException(thrown.get(0)); - } else if (thrown.size() > 1){ + } else if (thrown.size() > 1) { throw new CompositeCucumberException(thrown); } - bus.send(new TestRunFinished(bus.getTime(), bus.getTimeMillis())); + bus.send(new TestRunFinished(bus.getInstant())); } public byte exitStatus() { @@ -139,14 +142,13 @@ public static Builder builder() { public static class Builder { - private EventBus eventBus = new TimeServiceEventBus(TimeService.SYSTEM); + private EventBus eventBus = new TimeServiceEventBus(Clock.systemUTC()); private ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); private RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); private BackendSupplier backendSupplier; private ResourceLoader resourceLoader; - private ClassFinder classFinder; private FeatureSupplier featureSupplier; - private List additionalPlugins = Collections.emptyList(); + private List additionalPlugins = emptyList(); private Builder() { } @@ -166,11 +168,6 @@ public Builder withResourceLoader(final ResourceLoader resourceLoader) { return this; } - public Builder withClassFinder(final ClassFinder classFinder) { - this.classFinder = classFinder; - return this; - } - public Builder withBackendSupplier(final BackendSupplier backendSupplier) { this.backendSupplier = backendSupplier; return this; @@ -196,27 +193,35 @@ public Runtime build() { ? this.resourceLoader : new MultiLoader(this.classLoader); - final ClassFinder classFinder = this.classFinder != null - ? this.classFinder - : new ResourceLoaderClassFinder(resourceLoader, this.classLoader); + final ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, this.classLoader); + + final ObjectFactoryServiceLoader objectFactoryServiceLoader = new ObjectFactoryServiceLoader(runtimeOptions); + + final ObjectFactorySupplier objectFactorySupplier = runtimeOptions.isMultiThreaded() + ? new ThreadLocalObjectFactorySupplier(objectFactoryServiceLoader) + : new SingletonObjectFactorySupplier(objectFactoryServiceLoader); final BackendSupplier backendSupplier = this.backendSupplier != null ? this.backendSupplier - : new BackendModuleBackendSupplier(resourceLoader, classFinder, this.runtimeOptions); + : new BackendServiceLoader(resourceLoader, objectFactorySupplier); - final Plugins plugins = new Plugins(this.classLoader, new PluginFactory(), this.runtimeOptions); + final Plugins plugins = new Plugins(new PluginFactory(), runtimeOptions); for (final Plugin plugin : additionalPlugins) { plugins.addPlugin(plugin); } - if (this.runtimeOptions.isMultiThreaded()) { - plugins.setSerialEventBusOnEventListenerPlugins(this.eventBus); + final ExitStatus exitStatus = new ExitStatus(runtimeOptions); + plugins.addPlugin(exitStatus); + if (runtimeOptions.isMultiThreaded()) { + plugins.setSerialEventBusOnEventListenerPlugins(eventBus); } else { - plugins.setEventBusOnEventListenerPlugins(this.eventBus); + plugins.setEventBusOnEventListenerPlugins(eventBus); } + final TypeRegistryConfigurerSupplier typeRegistryConfigurerSupplier = new ScanningTypeRegistryConfigurerSupplier(classFinder, runtimeOptions); + final RunnerSupplier runnerSupplier = runtimeOptions.isMultiThreaded() - ? new ThreadLocalRunnerSupplier(this.runtimeOptions, eventBus, backendSupplier) - : new SingletonRunnerSupplier(this.runtimeOptions, eventBus, backendSupplier); + ? new ThreadLocalRunnerSupplier(runtimeOptions, eventBus, backendSupplier, objectFactorySupplier, typeRegistryConfigurerSupplier) + : new SingletonRunnerSupplier(runtimeOptions, eventBus, backendSupplier, objectFactorySupplier, typeRegistryConfigurerSupplier); final ExecutorService executor = runtimeOptions.isMultiThreaded() ? Executors.newFixedThreadPool(runtimeOptions.getThreads(), new CucumberThreadFactory()) @@ -227,11 +232,12 @@ public Runtime build() { final FeatureSupplier featureSupplier = this.featureSupplier != null ? this.featureSupplier - : new FeaturePathFeatureSupplier(featureLoader, this.runtimeOptions); + : new FeaturePathFeatureSupplier(featureLoader, runtimeOptions); - final Filters filters = new Filters(this.runtimeOptions); + final Filters filters = new Filters(runtimeOptions); final PickleOrder pickleOrder = runtimeOptions.getPickleOrder(); - return new Runtime(plugins, this.runtimeOptions, eventBus, filters, runnerSupplier, featureSupplier, executor, pickleOrder); + + return new Runtime(exitStatus, eventBus, filters, runnerSupplier, featureSupplier, executor, pickleOrder); } } @@ -283,4 +289,42 @@ public boolean awaitTermination(long timeout, TimeUnit unit) { return true; } } + + static final class ExitStatus implements ConcurrentEventListener { + private static final byte DEFAULT = 0x0; + private static final byte ERRORS = 0x1; + + private final List results = new ArrayList<>(); + private final RuntimeOptions runtimeOptions; + + private final EventHandler testCaseFinishedHandler = new EventHandler() { + @Override + public void receive(TestCaseFinished event) { + results.add(event.getResult()); + } + }; + + ExitStatus(RuntimeOptions runtimeOptions) { + this.runtimeOptions = runtimeOptions; + } + + @Override + public void setEventPublisher(EventPublisher publisher) { + publisher.registerHandlerFor(TestCaseFinished.class, testCaseFinishedHandler); + } + + byte exitStatus() { + if (results.isEmpty()) { + return DEFAULT; + } + + if (runtimeOptions.isWip()) { + Result leastSeverResult = min(results, comparing(Result::getStatus)); + return leastSeverResult.getStatus().is(Status.PASSED) ? ERRORS : DEFAULT; + } else { + Result mostSevereResult = max(results, comparing(Result::getStatus)); + return mostSevereResult.getStatus().isOk(runtimeOptions.isStrict()) ? DEFAULT : ERRORS; + } + } + } } diff --git a/core/src/main/java/io/cucumber/core/runtime/ScanningTypeRegistryConfigurerSupplier.java b/core/src/main/java/io/cucumber/core/runtime/ScanningTypeRegistryConfigurerSupplier.java new file mode 100644 index 0000000000..dc51894d4c --- /dev/null +++ b/core/src/main/java/io/cucumber/core/runtime/ScanningTypeRegistryConfigurerSupplier.java @@ -0,0 +1,40 @@ +package io.cucumber.core.runtime; + +import io.cucumber.core.api.TypeRegistryConfigurer; +import io.cucumber.core.io.ClassFinder; +import io.cucumber.core.reflection.Reflections; +import io.cucumber.core.runner.Options; + +import java.util.Locale; + +public final class ScanningTypeRegistryConfigurerSupplier implements TypeRegistryConfigurerSupplier { + + private final ClassFinder classFinder; + private final Options options; + + public ScanningTypeRegistryConfigurerSupplier(ClassFinder classFinder, Options options) { + this.classFinder = classFinder; + this.options = options; + } + + @Override + public TypeRegistryConfigurer get() { + Reflections reflections = new Reflections(classFinder); + return reflections.instantiateExactlyOneSubclass(TypeRegistryConfigurer.class, options.getGlue(), new Class[0], new Object[0], new DefaultTypeRegistryConfiguration()); + } + + private static final class DefaultTypeRegistryConfiguration implements TypeRegistryConfigurer { + + @Override + public Locale locale() { + return Locale.ENGLISH; + } + + @Override + public void configureTypeRegistry(io.cucumber.core.api.TypeRegistry typeRegistry) { + //noop + } + + } + +} diff --git a/core/src/main/java/io/cucumber/core/runtime/SingletonObjectFactorySupplier.java b/core/src/main/java/io/cucumber/core/runtime/SingletonObjectFactorySupplier.java new file mode 100644 index 0000000000..f970341e82 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/runtime/SingletonObjectFactorySupplier.java @@ -0,0 +1,22 @@ +package io.cucumber.core.runtime; + +import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.backend.ObjectFactoryServiceLoader; + +public final class SingletonObjectFactorySupplier implements ObjectFactorySupplier { + + private final ObjectFactoryServiceLoader objectFactoryServiceLoader; + private ObjectFactory objectFactory; + + public SingletonObjectFactorySupplier(ObjectFactoryServiceLoader objectFactoryServiceLoader) { + this.objectFactoryServiceLoader = objectFactoryServiceLoader; + } + + @Override + public ObjectFactory get() { + if (objectFactory == null) { + objectFactory = objectFactoryServiceLoader.loadObjectFactory(); + } + return objectFactory; + } +} diff --git a/core/src/main/java/io/cucumber/core/runtime/SingletonRunnerSupplier.java b/core/src/main/java/io/cucumber/core/runtime/SingletonRunnerSupplier.java new file mode 100644 index 0000000000..93e57da1ec --- /dev/null +++ b/core/src/main/java/io/cucumber/core/runtime/SingletonRunnerSupplier.java @@ -0,0 +1,53 @@ +package io.cucumber.core.runtime; + +import io.cucumber.core.eventbus.EventBus; +import io.cucumber.core.runner.Options; +import io.cucumber.core.runner.Runner; + +/** + * Returns a single unique runner. + *

+ * Not thread safe. + */ +public final class SingletonRunnerSupplier implements RunnerSupplier { + + private final BackendSupplier backendSupplier; + private final Options runnerOptions; + private final EventBus eventBus; + private final ObjectFactorySupplier objectFactorySupplier; + private final TypeRegistryConfigurerSupplier typeRegistryConfigurerSupplier; + + + public SingletonRunnerSupplier( + Options runnerOptions, + EventBus eventBus, + BackendSupplier backendSupplier, + ObjectFactorySupplier objectFactorySupplier, TypeRegistryConfigurerSupplier typeRegistryConfigurerSupplier) { + this.backendSupplier = backendSupplier; + this.runnerOptions = runnerOptions; + this.eventBus = eventBus; + this.objectFactorySupplier = objectFactorySupplier; + this.typeRegistryConfigurerSupplier = typeRegistryConfigurerSupplier; + } + + private Runner runner; + + @Override + public Runner get() { + if (runner == null) { + runner = createRunner(); + } + return runner; + } + + private Runner createRunner() { + return new Runner( + eventBus, + backendSupplier.get(), + objectFactorySupplier.get(), + typeRegistryConfigurerSupplier.get(), + runnerOptions + ); + } + +} diff --git a/core/src/main/java/io/cucumber/core/runtime/ThreadLocalObjectFactorySupplier.java b/core/src/main/java/io/cucumber/core/runtime/ThreadLocalObjectFactorySupplier.java new file mode 100644 index 0000000000..9ae6eff736 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/runtime/ThreadLocalObjectFactorySupplier.java @@ -0,0 +1,22 @@ +package io.cucumber.core.runtime; + + +import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.backend.ObjectFactoryServiceLoader; + +import static java.lang.ThreadLocal.withInitial; +import static java.util.Objects.requireNonNull; + +public final class ThreadLocalObjectFactorySupplier implements ObjectFactorySupplier { + + private final ThreadLocal runners; + + public ThreadLocalObjectFactorySupplier(ObjectFactoryServiceLoader objectFactoryServiceLoader) { + this.runners = withInitial(requireNonNull(objectFactoryServiceLoader)::loadObjectFactory); + } + + @Override + public ObjectFactory get() { + return runners.get(); + } +} diff --git a/core/src/main/java/cucumber/runner/ThreadLocalRunnerSupplier.java b/core/src/main/java/io/cucumber/core/runtime/ThreadLocalRunnerSupplier.java similarity index 61% rename from core/src/main/java/cucumber/runner/ThreadLocalRunnerSupplier.java rename to core/src/main/java/io/cucumber/core/runtime/ThreadLocalRunnerSupplier.java index 47978368c1..cc0e6db910 100644 --- a/core/src/main/java/cucumber/runner/ThreadLocalRunnerSupplier.java +++ b/core/src/main/java/io/cucumber/core/runtime/ThreadLocalRunnerSupplier.java @@ -1,36 +1,41 @@ -package cucumber.runner; +package io.cucumber.core.runtime; -import cucumber.api.event.Event; -import cucumber.api.event.EventHandler; -import cucumber.runtime.BackendSupplier; -import io.cucumber.core.options.RunnerOptions; +import io.cucumber.core.event.Event; +import io.cucumber.core.event.EventHandler; +import io.cucumber.core.eventbus.AbstractEventBus; +import io.cucumber.core.eventbus.EventBus; +import io.cucumber.core.runner.Options; +import io.cucumber.core.runner.Runner; + +import java.time.Instant; /** * Creates a distinct runner for each calling thread. Each runner has its own bus, backend- and glue-suppliers. *

* Each runners bus passes all events to the event bus of this supplier. */ -public class ThreadLocalRunnerSupplier implements RunnerSupplier { +public final class ThreadLocalRunnerSupplier implements RunnerSupplier { private final BackendSupplier backendSupplier; - private final RunnerOptions runnerOptions; + private final io.cucumber.core.runner.Options runnerOptions; private final SynchronizedEventBus sharedEventBus; + private final ObjectFactorySupplier objectFactorySupplier; + private final TypeRegistryConfigurerSupplier typeRegistryConfigurerSupplier; - private final ThreadLocal runners = new ThreadLocal() { - @Override - protected Runner initialValue() { - return createRunner(); - } - }; + private final ThreadLocal runners = ThreadLocal.withInitial(this::createRunner); public ThreadLocalRunnerSupplier( - RunnerOptions runnerOptions, + Options runnerOptions, EventBus sharedEventBus, - BackendSupplier backendSupplier + BackendSupplier backendSupplier, + ObjectFactorySupplier objectFactorySupplier, + TypeRegistryConfigurerSupplier typeRegistryConfigurerSupplier ) { this.runnerOptions = runnerOptions; this.sharedEventBus = SynchronizedEventBus.synchronize(sharedEventBus); this.backendSupplier = backendSupplier; + this.objectFactorySupplier = objectFactorySupplier; + this.typeRegistryConfigurerSupplier = typeRegistryConfigurerSupplier; } @Override @@ -39,7 +44,13 @@ public Runner get() { } private Runner createRunner() { - return new Runner(new LocalEventBus(sharedEventBus), backendSupplier.get(), runnerOptions); + return new Runner( + new LocalEventBus(sharedEventBus), + backendSupplier.get(), + objectFactorySupplier.get(), + typeRegistryConfigurerSupplier.get(), + runnerOptions + ); } private static final class LocalEventBus extends AbstractEventBus { @@ -50,11 +61,6 @@ private static final class LocalEventBus extends AbstractEventBus { this.parent = parent; } - @Override - public Long getTime() { - return parent.getTime(); - } - @Override public void send(final Event event) { super.send(event); @@ -62,8 +68,8 @@ public void send(final Event event) { } @Override - public Long getTimeMillis() { - return parent.getTimeMillis(); + public Instant getInstant() { + return parent.getInstant(); } } @@ -83,11 +89,6 @@ private SynchronizedEventBus(final EventBus delegate) { this.delegate = delegate; } - @Override - public synchronized Long getTime() { - return delegate.getTime(); - } - @Override public synchronized void send(final Event event) { delegate.send(event); @@ -109,8 +110,8 @@ public synchronized void removeHandlerFor(Class eventType, } @Override - public Long getTimeMillis() { - return delegate.getTimeMillis(); + public Instant getInstant() { + return delegate.getInstant(); } } } diff --git a/core/src/main/java/io/cucumber/core/runtime/TimeServiceEventBus.java b/core/src/main/java/io/cucumber/core/runtime/TimeServiceEventBus.java new file mode 100644 index 0000000000..3d2789f98e --- /dev/null +++ b/core/src/main/java/io/cucumber/core/runtime/TimeServiceEventBus.java @@ -0,0 +1,19 @@ +package io.cucumber.core.runtime; + +import java.time.Clock; +import java.time.Instant; + +import io.cucumber.core.eventbus.AbstractEventBus; + +public final class TimeServiceEventBus extends AbstractEventBus { + private final Clock clock; + + public TimeServiceEventBus(Clock clock) { + this.clock = clock; + } + + @Override + public Instant getInstant() { + return clock.instant(); + } +} diff --git a/core/src/main/java/io/cucumber/core/runtime/TypeRegistryConfigurerSupplier.java b/core/src/main/java/io/cucumber/core/runtime/TypeRegistryConfigurerSupplier.java new file mode 100644 index 0000000000..18d77a2d4c --- /dev/null +++ b/core/src/main/java/io/cucumber/core/runtime/TypeRegistryConfigurerSupplier.java @@ -0,0 +1,7 @@ +package io.cucumber.core.runtime; + +import io.cucumber.core.api.TypeRegistryConfigurer; + +public interface TypeRegistryConfigurerSupplier { + TypeRegistryConfigurer get(); +} diff --git a/core/src/main/java/cucumber/runtime/snippets/ArgumentPattern.java b/core/src/main/java/io/cucumber/core/snippets/ArgumentPattern.java similarity index 92% rename from core/src/main/java/cucumber/runtime/snippets/ArgumentPattern.java rename to core/src/main/java/io/cucumber/core/snippets/ArgumentPattern.java index 83b378b5cf..c1e27ba7d9 100644 --- a/core/src/main/java/cucumber/runtime/snippets/ArgumentPattern.java +++ b/core/src/main/java/io/cucumber/core/snippets/ArgumentPattern.java @@ -1,9 +1,9 @@ -package cucumber.runtime.snippets; +package io.cucumber.core.snippets; import java.util.regex.Matcher; import java.util.regex.Pattern; -class ArgumentPattern { +final class ArgumentPattern { private final Pattern pattern; private final String replacement; diff --git a/core/src/main/java/cucumber/runtime/snippets/CamelCaseConcatenator.java b/core/src/main/java/io/cucumber/core/snippets/CamelCaseJoiner.java similarity index 85% rename from core/src/main/java/cucumber/runtime/snippets/CamelCaseConcatenator.java rename to core/src/main/java/io/cucumber/core/snippets/CamelCaseJoiner.java index e05e031e4c..b46228a21b 100644 --- a/core/src/main/java/cucumber/runtime/snippets/CamelCaseConcatenator.java +++ b/core/src/main/java/io/cucumber/core/snippets/CamelCaseJoiner.java @@ -1,6 +1,7 @@ -package cucumber.runtime.snippets; +package io.cucumber.core.snippets; + +final class CamelCaseJoiner implements Joiner { -public class CamelCaseConcatenator implements Concatenator { @Override public String concatenate(String[] words) { StringBuilder functionName = new StringBuilder(); diff --git a/core/src/main/java/cucumber/runtime/snippets/FunctionNameGenerator.java b/core/src/main/java/io/cucumber/core/snippets/FunctionNameGenerator.java similarity index 74% rename from core/src/main/java/cucumber/runtime/snippets/FunctionNameGenerator.java rename to core/src/main/java/io/cucumber/core/snippets/FunctionNameGenerator.java index 7a4e77f7a5..49eed36a6f 100644 --- a/core/src/main/java/cucumber/runtime/snippets/FunctionNameGenerator.java +++ b/core/src/main/java/io/cucumber/core/snippets/FunctionNameGenerator.java @@ -1,20 +1,20 @@ -package cucumber.runtime.snippets; +package io.cucumber.core.snippets; -public class FunctionNameGenerator { +final class FunctionNameGenerator { private static final Character SUBST = ' '; - private final Concatenator concatenator; + private final Joiner joiner; - public FunctionNameGenerator(Concatenator concatenator) { - this.concatenator = concatenator; + FunctionNameGenerator(Joiner joiner) { + this.joiner = joiner; } - public String generateFunctionName(String sentence) { + String generateFunctionName(String sentence) { sentence = removeIllegalCharacters(sentence); sentence = sentence.trim(); String[] words = sentence.split("\\s"); - return concatenator.concatenate(words); + return joiner.concatenate(words); } private String removeIllegalCharacters(String sentence) { diff --git a/core/src/main/java/io/cucumber/core/snippets/Joiner.java b/core/src/main/java/io/cucumber/core/snippets/Joiner.java new file mode 100644 index 0000000000..2141a421a3 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/snippets/Joiner.java @@ -0,0 +1,5 @@ +package io.cucumber.core.snippets; + +interface Joiner { + String concatenate(String[] words); +} diff --git a/core/src/main/java/cucumber/runtime/snippets/Snippet.java b/core/src/main/java/io/cucumber/core/snippets/Snippet.java similarity index 88% rename from core/src/main/java/cucumber/runtime/snippets/Snippet.java rename to core/src/main/java/io/cucumber/core/snippets/Snippet.java index 91805708d5..47a70fb2b8 100644 --- a/core/src/main/java/cucumber/runtime/snippets/Snippet.java +++ b/core/src/main/java/io/cucumber/core/snippets/Snippet.java @@ -1,13 +1,16 @@ -package cucumber.runtime.snippets; +package io.cucumber.core.snippets; + +import org.apiguardian.api.API; import java.lang.reflect.Type; +import java.text.MessageFormat; import java.util.Map; +@API(status = API.Status.STABLE) 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)}
  • @@ -17,7 +20,7 @@ public interface Snippet { *
  • {5} : value of {@link #tableHint()} if the step has a table
  • *
*/ - String template(); + MessageFormat template(); /** * @return a hint about alternative ways to declare a table argument diff --git a/core/src/main/java/cucumber/runtime/snippets/SnippetGenerator.java b/core/src/main/java/io/cucumber/core/snippets/SnippetGenerator.java similarity index 81% rename from core/src/main/java/cucumber/runtime/snippets/SnippetGenerator.java rename to core/src/main/java/io/cucumber/core/snippets/SnippetGenerator.java index ecf285da0b..f7edc0c6f9 100644 --- a/core/src/main/java/cucumber/runtime/snippets/SnippetGenerator.java +++ b/core/src/main/java/io/cucumber/core/snippets/SnippetGenerator.java @@ -1,14 +1,14 @@ -package cucumber.runtime.snippets; +package io.cucumber.core.snippets; -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.GeneratedExpression; +import io.cucumber.cucumberexpressions.ParameterType; import io.cucumber.cucumberexpressions.ParameterTypeRegistry; +import io.cucumber.datatable.DataTable; import java.lang.reflect.Type; import java.util.ArrayList; @@ -17,9 +17,7 @@ import java.util.Map; import java.util.regex.Pattern; -import static java.text.MessageFormat.format; - -public class SnippetGenerator { +public final class SnippetGenerator { @SuppressWarnings("RegExpRedundantEscape") // Android can't parse unescaped braces. private static final ArgumentPattern[] DEFAULT_ARGUMENT_PATTERNS = new ArgumentPattern[]{ new ArgumentPattern(Pattern.compile("\\{.*?\\}")) @@ -35,19 +33,19 @@ public SnippetGenerator(Snippet snippet, ParameterTypeRegistry parameterTypeRegi this.generator = new CucumberExpressionGenerator(parameterTypeRegistry); } - public List getSnippet(PickleStep step, String keyword, FunctionNameGenerator functionNameGenerator) { + public List getSnippet(PickleStep step, String keyword, SnippetType snippetType) { List generatedExpressions = generator.generateExpressions(step.getText()); List snippets = new ArrayList<>(generatedExpressions.size()); - + FunctionNameGenerator functionNameGenerator = new FunctionNameGenerator(snippetType.joiner()); for (GeneratedExpression expression : generatedExpressions) { - snippets.add(format( - 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() : "" + snippets.add(snippet.template().format(new String[]{ + 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() : "" + } )); } diff --git a/core/src/main/java/io/cucumber/core/snippets/SnippetType.java b/core/src/main/java/io/cucumber/core/snippets/SnippetType.java new file mode 100644 index 0000000000..813a617622 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/snippets/SnippetType.java @@ -0,0 +1,16 @@ +package io.cucumber.core.snippets; + +public enum SnippetType { + UNDERSCORE(new UnderscoreJoiner()), + CAMELCASE(new CamelCaseJoiner()); + + private final Joiner joiner; + + SnippetType(Joiner joiner) { + this.joiner = joiner; + } + + Joiner joiner() { + return joiner; + } +} diff --git a/core/src/main/java/cucumber/runtime/snippets/UnderscoreConcatenator.java b/core/src/main/java/io/cucumber/core/snippets/UnderscoreJoiner.java similarity index 82% rename from core/src/main/java/cucumber/runtime/snippets/UnderscoreConcatenator.java rename to core/src/main/java/io/cucumber/core/snippets/UnderscoreJoiner.java index 30dfc8ad4e..0b3cebc62c 100644 --- a/core/src/main/java/cucumber/runtime/snippets/UnderscoreConcatenator.java +++ b/core/src/main/java/io/cucumber/core/snippets/UnderscoreJoiner.java @@ -1,6 +1,6 @@ -package cucumber.runtime.snippets; +package io.cucumber.core.snippets; -public class UnderscoreConcatenator implements Concatenator { +class UnderscoreJoiner implements Joiner { @Override public String concatenate(String[] words) { StringBuilder functionName = new StringBuilder(); diff --git a/core/src/main/java/io/cucumber/stepexpression/Argument.java b/core/src/main/java/io/cucumber/core/stepexpression/Argument.java similarity index 57% rename from core/src/main/java/io/cucumber/stepexpression/Argument.java rename to core/src/main/java/io/cucumber/core/stepexpression/Argument.java index 963c2b3498..745df56f01 100644 --- a/core/src/main/java/io/cucumber/stepexpression/Argument.java +++ b/core/src/main/java/io/cucumber/core/stepexpression/Argument.java @@ -1,4 +1,4 @@ -package io.cucumber.stepexpression; +package io.cucumber.core.stepexpression; public interface Argument { diff --git a/core/src/main/java/io/cucumber/stepexpression/ExpressionArgumentMatcher.java b/core/src/main/java/io/cucumber/core/stepexpression/ArgumentMatcher.java similarity index 83% rename from core/src/main/java/io/cucumber/stepexpression/ExpressionArgumentMatcher.java rename to core/src/main/java/io/cucumber/core/stepexpression/ArgumentMatcher.java index bc1ee9fc90..bd67411c52 100644 --- a/core/src/main/java/io/cucumber/stepexpression/ExpressionArgumentMatcher.java +++ b/core/src/main/java/io/cucumber/core/stepexpression/ArgumentMatcher.java @@ -1,4 +1,4 @@ -package io.cucumber.stepexpression; +package io.cucumber.core.stepexpression; import gherkin.pickles.PickleStep; import gherkin.pickles.PickleString; @@ -7,15 +7,14 @@ import java.lang.reflect.Type; import java.util.List; -public class ExpressionArgumentMatcher implements ArgumentMatcher { +public final class ArgumentMatcher { private final StepExpression expression; - public ExpressionArgumentMatcher(StepExpression expression) { + public ArgumentMatcher(StepExpression expression) { this.expression = expression; } - @Override public List argumentsFrom(PickleStep step, Type... types) { if (step.getArgument().isEmpty()) { return expression.match(step.getText(), types); diff --git a/core/src/main/java/io/cucumber/stepexpression/DataTableArgument.java b/core/src/main/java/io/cucumber/core/stepexpression/DataTableArgument.java similarity index 94% rename from core/src/main/java/io/cucumber/stepexpression/DataTableArgument.java rename to core/src/main/java/io/cucumber/core/stepexpression/DataTableArgument.java index 94304643ad..f4a56a6fd8 100644 --- a/core/src/main/java/io/cucumber/stepexpression/DataTableArgument.java +++ b/core/src/main/java/io/cucumber/core/stepexpression/DataTableArgument.java @@ -1,4 +1,4 @@ -package io.cucumber.stepexpression; +package io.cucumber.core.stepexpression; import io.cucumber.datatable.DataTable; diff --git a/core/src/main/java/io/cucumber/stepexpression/DocStringArgument.java b/core/src/main/java/io/cucumber/core/stepexpression/DocStringArgument.java similarity index 92% rename from core/src/main/java/io/cucumber/stepexpression/DocStringArgument.java rename to core/src/main/java/io/cucumber/core/stepexpression/DocStringArgument.java index c803dd09fa..12c55d29d1 100644 --- a/core/src/main/java/io/cucumber/stepexpression/DocStringArgument.java +++ b/core/src/main/java/io/cucumber/core/stepexpression/DocStringArgument.java @@ -1,4 +1,4 @@ -package io.cucumber.stepexpression; +package io.cucumber.core.stepexpression; public final class DocStringArgument implements Argument { diff --git a/core/src/main/java/io/cucumber/stepexpression/DocStringTransformer.java b/core/src/main/java/io/cucumber/core/stepexpression/DocStringTransformer.java similarity index 64% rename from core/src/main/java/io/cucumber/stepexpression/DocStringTransformer.java rename to core/src/main/java/io/cucumber/core/stepexpression/DocStringTransformer.java index 75488a1c46..08f74d0821 100644 --- a/core/src/main/java/io/cucumber/stepexpression/DocStringTransformer.java +++ b/core/src/main/java/io/cucumber/core/stepexpression/DocStringTransformer.java @@ -1,4 +1,4 @@ -package io.cucumber.stepexpression; +package io.cucumber.core.stepexpression; interface DocStringTransformer { diff --git a/core/src/main/java/io/cucumber/stepexpression/ExpressionArgument.java b/core/src/main/java/io/cucumber/core/stepexpression/ExpressionArgument.java similarity index 93% rename from core/src/main/java/io/cucumber/stepexpression/ExpressionArgument.java rename to core/src/main/java/io/cucumber/core/stepexpression/ExpressionArgument.java index c3d6eadd6c..5ec5e8c355 100644 --- a/core/src/main/java/io/cucumber/stepexpression/ExpressionArgument.java +++ b/core/src/main/java/io/cucumber/core/stepexpression/ExpressionArgument.java @@ -1,4 +1,4 @@ -package io.cucumber.stepexpression; +package io.cucumber.core.stepexpression; import io.cucumber.cucumberexpressions.Group; diff --git a/core/src/main/java/io/cucumber/stepexpression/PickleTableConverter.java b/core/src/main/java/io/cucumber/core/stepexpression/PickleTableConverter.java similarity index 83% rename from core/src/main/java/io/cucumber/stepexpression/PickleTableConverter.java rename to core/src/main/java/io/cucumber/core/stepexpression/PickleTableConverter.java index 62ea55956b..24354ee247 100644 --- a/core/src/main/java/io/cucumber/stepexpression/PickleTableConverter.java +++ b/core/src/main/java/io/cucumber/core/stepexpression/PickleTableConverter.java @@ -1,4 +1,4 @@ -package io.cucumber.stepexpression; +package io.cucumber.core.stepexpression; import gherkin.pickles.PickleCell; import gherkin.pickles.PickleRow; @@ -7,7 +7,12 @@ import java.util.ArrayList; import java.util.List; -class PickleTableConverter { +final class PickleTableConverter { + + private PickleTableConverter() { + + } + static List> toTable(PickleTable pickleTable) { List> table = new ArrayList>(); for (PickleRow pickleRow : pickleTable.getRows()) { diff --git a/core/src/main/java/io/cucumber/stepexpression/RawTableTransformer.java b/core/src/main/java/io/cucumber/core/stepexpression/RawTableTransformer.java similarity index 71% rename from core/src/main/java/io/cucumber/stepexpression/RawTableTransformer.java rename to core/src/main/java/io/cucumber/core/stepexpression/RawTableTransformer.java index 9071ad0938..594e9c3896 100644 --- a/core/src/main/java/io/cucumber/stepexpression/RawTableTransformer.java +++ b/core/src/main/java/io/cucumber/core/stepexpression/RawTableTransformer.java @@ -1,4 +1,4 @@ -package io.cucumber.stepexpression; +package io.cucumber.core.stepexpression; import java.util.List; diff --git a/core/src/main/java/io/cucumber/stepexpression/StepExpression.java b/core/src/main/java/io/cucumber/core/stepexpression/StepExpression.java similarity index 97% rename from core/src/main/java/io/cucumber/stepexpression/StepExpression.java rename to core/src/main/java/io/cucumber/core/stepexpression/StepExpression.java index f7c80e5e84..f49a45c37e 100644 --- a/core/src/main/java/io/cucumber/stepexpression/StepExpression.java +++ b/core/src/main/java/io/cucumber/core/stepexpression/StepExpression.java @@ -1,4 +1,4 @@ -package io.cucumber.stepexpression; +package io.cucumber.core.stepexpression; import io.cucumber.cucumberexpressions.Expression; diff --git a/core/src/main/java/io/cucumber/stepexpression/StepExpressionFactory.java b/core/src/main/java/io/cucumber/core/stepexpression/StepExpressionFactory.java similarity index 98% rename from core/src/main/java/io/cucumber/stepexpression/StepExpressionFactory.java rename to core/src/main/java/io/cucumber/core/stepexpression/StepExpressionFactory.java index b3ba4e7da2..4490018b64 100644 --- a/core/src/main/java/io/cucumber/stepexpression/StepExpressionFactory.java +++ b/core/src/main/java/io/cucumber/core/stepexpression/StepExpressionFactory.java @@ -1,11 +1,10 @@ -package io.cucumber.stepexpression; +package io.cucumber.core.stepexpression; -import cucumber.runtime.CucumberException; +import io.cucumber.core.exception.CucumberException; +import io.cucumber.cucumberexpressions.Expression; import io.cucumber.cucumberexpressions.UndefinedParameterTypeException; -import io.cucumber.datatable.DataTableTypeRegistryTableConverter; import io.cucumber.datatable.DataTable; - -import io.cucumber.cucumberexpressions.Expression; +import io.cucumber.datatable.DataTableTypeRegistryTableConverter; import java.lang.reflect.Type; import java.util.List; diff --git a/core/src/main/java/io/cucumber/stepexpression/TypeRegistry.java b/core/src/main/java/io/cucumber/core/stepexpression/TypeRegistry.java similarity index 93% rename from core/src/main/java/io/cucumber/stepexpression/TypeRegistry.java rename to core/src/main/java/io/cucumber/core/stepexpression/TypeRegistry.java index d9167f601d..4055fdc888 100644 --- a/core/src/main/java/io/cucumber/stepexpression/TypeRegistry.java +++ b/core/src/main/java/io/cucumber/core/stepexpression/TypeRegistry.java @@ -1,4 +1,4 @@ -package io.cucumber.stepexpression; +package io.cucumber.core.stepexpression; import io.cucumber.cucumberexpressions.ParameterByTypeTransformer; import io.cucumber.cucumberexpressions.ParameterType; @@ -10,7 +10,7 @@ import java.util.Locale; -public final class TypeRegistry implements cucumber.api.TypeRegistry, io.cucumber.core.api.TypeRegistry { +public final class TypeRegistry implements io.cucumber.core.api.TypeRegistry { private final ParameterTypeRegistry parameterTypeRegistry; diff --git a/core/src/main/java/io/cucumber/stepexpression/TypeResolver.java b/core/src/main/java/io/cucumber/core/stepexpression/TypeResolver.java similarity index 90% rename from core/src/main/java/io/cucumber/stepexpression/TypeResolver.java rename to core/src/main/java/io/cucumber/core/stepexpression/TypeResolver.java index 467cf212a1..0ae6777cb7 100644 --- a/core/src/main/java/io/cucumber/stepexpression/TypeResolver.java +++ b/core/src/main/java/io/cucumber/core/stepexpression/TypeResolver.java @@ -1,4 +1,4 @@ -package io.cucumber.stepexpression; +package io.cucumber.core.stepexpression; import java.lang.reflect.Type; diff --git a/core/src/main/java/io/cucumber/stepexpression/ArgumentMatcher.java b/core/src/main/java/io/cucumber/stepexpression/ArgumentMatcher.java deleted file mode 100644 index c2b7a2a322..0000000000 --- a/core/src/main/java/io/cucumber/stepexpression/ArgumentMatcher.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.cucumber.stepexpression; - -import gherkin.pickles.PickleStep; - -import java.lang.reflect.Type; -import java.util.List; - -public interface ArgumentMatcher { - List argumentsFrom(PickleStep step, Type... types); -} diff --git a/core/src/main/resources/io/cucumber/core/options/USAGE.txt b/core/src/main/resources/io/cucumber/core/options/USAGE.txt index b8739c70c7..70107638c4 100644 --- a/core/src/main/resources/io/cucumber/core/options/USAGE.txt +++ b/core/src/main/resources/io/cucumber/core/options/USAGE.txt @@ -1,4 +1,4 @@ -Usage: java cucumber.api.cli.Main [options] [ [DIR|DIR URI] | [ [FILE|FILE URI][:LINE]* ] | @[FILE|FILE URI] ]+ +Usage: java io.cucumber.core.cli.Main [options] [ [DIR|DIR URI] | [ [FILE|FILE URI][:LINE]* ] | @[FILE|FILE URI] ]+ Options: @@ -39,10 +39,6 @@ Options: --i18n LANG List keywords for in a particular language Run with "--i18n help" to see all languages - --junit,OPTION[,OPTION]* Pass the OPTION(s) to the JUnit module. - Use --junit,-h or --junit,--help to print the - options of the JUnit module. - -w, --wip Fail if there are any passing scenarios. @@ -54,6 +50,12 @@ Options: --count Number of scenarios to be executed. If not specified all scenarios are run. + --object-factory CLASSNAME Uses the class specified by CLASSNAME as + object factory. Be aware that the class is + loaded through a service loader and therefore + also needs to be specified in: + META-INF/services/io.cucumber.core.backend.ObjectFactory + Feature path examples: Load the files with the extension ".feature" diff --git a/core/src/main/resources/io/cucumber/formatter/html/bubble_256x256.png b/core/src/main/resources/io/cucumber/core/plugin/html/bubble_256x256.png similarity index 100% rename from core/src/main/resources/io/cucumber/formatter/html/bubble_256x256.png rename to core/src/main/resources/io/cucumber/core/plugin/html/bubble_256x256.png diff --git a/core/src/main/resources/io/cucumber/formatter/html/formatter.js b/core/src/main/resources/io/cucumber/core/plugin/html/formatter.js similarity index 100% rename from core/src/main/resources/io/cucumber/formatter/html/formatter.js rename to core/src/main/resources/io/cucumber/core/plugin/html/formatter.js diff --git a/core/src/main/resources/io/cucumber/formatter/html/index.html b/core/src/main/resources/io/cucumber/core/plugin/html/index.html similarity index 100% rename from core/src/main/resources/io/cucumber/formatter/html/index.html rename to core/src/main/resources/io/cucumber/core/plugin/html/index.html diff --git a/core/src/main/resources/io/cucumber/formatter/html/jquery-1.8.2.min.js b/core/src/main/resources/io/cucumber/core/plugin/html/jquery-1.8.2.min.js similarity index 100% rename from core/src/main/resources/io/cucumber/formatter/html/jquery-1.8.2.min.js rename to core/src/main/resources/io/cucumber/core/plugin/html/jquery-1.8.2.min.js diff --git a/core/src/main/resources/io/cucumber/formatter/html/report.js b/core/src/main/resources/io/cucumber/core/plugin/html/report.js similarity index 100% rename from core/src/main/resources/io/cucumber/formatter/html/report.js rename to core/src/main/resources/io/cucumber/core/plugin/html/report.js diff --git a/core/src/main/resources/io/cucumber/formatter/html/style.css b/core/src/main/resources/io/cucumber/core/plugin/html/style.css similarity index 100% rename from core/src/main/resources/io/cucumber/formatter/html/style.css rename to core/src/main/resources/io/cucumber/core/plugin/html/style.css diff --git a/core/src/main/resources/io/cucumber/formatter/timeline/chosen-sprite.png b/core/src/main/resources/io/cucumber/core/plugin/timeline/chosen-sprite.png similarity index 100% rename from core/src/main/resources/io/cucumber/formatter/timeline/chosen-sprite.png rename to core/src/main/resources/io/cucumber/core/plugin/timeline/chosen-sprite.png diff --git a/core/src/main/resources/io/cucumber/formatter/timeline/chosen.jquery.min.js b/core/src/main/resources/io/cucumber/core/plugin/timeline/chosen.jquery.min.js similarity index 100% rename from core/src/main/resources/io/cucumber/formatter/timeline/chosen.jquery.min.js rename to core/src/main/resources/io/cucumber/core/plugin/timeline/chosen.jquery.min.js diff --git a/core/src/main/resources/io/cucumber/formatter/timeline/chosen.min.css b/core/src/main/resources/io/cucumber/core/plugin/timeline/chosen.min.css similarity index 100% rename from core/src/main/resources/io/cucumber/formatter/timeline/chosen.min.css rename to core/src/main/resources/io/cucumber/core/plugin/timeline/chosen.min.css diff --git a/core/src/main/resources/io/cucumber/formatter/timeline/chosen.override.css b/core/src/main/resources/io/cucumber/core/plugin/timeline/chosen.override.css similarity index 100% rename from core/src/main/resources/io/cucumber/formatter/timeline/chosen.override.css rename to core/src/main/resources/io/cucumber/core/plugin/timeline/chosen.override.css diff --git a/core/src/main/resources/io/cucumber/formatter/timeline/formatter.js b/core/src/main/resources/io/cucumber/core/plugin/timeline/formatter.js similarity index 100% rename from core/src/main/resources/io/cucumber/formatter/timeline/formatter.js rename to core/src/main/resources/io/cucumber/core/plugin/timeline/formatter.js diff --git a/core/src/main/resources/io/cucumber/formatter/timeline/index.html b/core/src/main/resources/io/cucumber/core/plugin/timeline/index.html similarity index 100% rename from core/src/main/resources/io/cucumber/formatter/timeline/index.html rename to core/src/main/resources/io/cucumber/core/plugin/timeline/index.html diff --git a/core/src/main/resources/io/cucumber/formatter/timeline/jquery-3.3.1.min.js b/core/src/main/resources/io/cucumber/core/plugin/timeline/jquery-3.3.1.min.js similarity index 100% rename from core/src/main/resources/io/cucumber/formatter/timeline/jquery-3.3.1.min.js rename to core/src/main/resources/io/cucumber/core/plugin/timeline/jquery-3.3.1.min.js diff --git a/core/src/main/resources/io/cucumber/formatter/timeline/report.css b/core/src/main/resources/io/cucumber/core/plugin/timeline/report.css similarity index 100% rename from core/src/main/resources/io/cucumber/formatter/timeline/report.css rename to core/src/main/resources/io/cucumber/core/plugin/timeline/report.css diff --git a/core/src/main/resources/io/cucumber/formatter/timeline/vis.min.css b/core/src/main/resources/io/cucumber/core/plugin/timeline/vis.min.css similarity index 100% rename from core/src/main/resources/io/cucumber/formatter/timeline/vis.min.css rename to core/src/main/resources/io/cucumber/core/plugin/timeline/vis.min.css diff --git a/core/src/main/resources/io/cucumber/formatter/timeline/vis.min.js b/core/src/main/resources/io/cucumber/core/plugin/timeline/vis.min.js similarity index 93% rename from core/src/main/resources/io/cucumber/formatter/timeline/vis.min.js rename to core/src/main/resources/io/cucumber/core/plugin/timeline/vis.min.js index 1136c8a151..b8c6d0243e 100644 --- a/core/src/main/resources/io/cucumber/formatter/timeline/vis.min.js +++ b/core/src/main/resources/io/cucumber/core/plugin/timeline/vis.min.js @@ -24,7 +24,7 @@ */ "use strict";!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.vis=e():t.vis=e()}(this,function(){return function(t){function e(o){if(i[o])return i[o].exports;var n=i[o]={i:o,l:!1,exports:{}};return t[o].call(n.exports,n,n.exports,e),n.l=!0,n.exports}var i={};return e.m=t,e.c=i,e.d=function(t,i,o){e.o(t,i)||Object.defineProperty(t,i,{configurable:!1,enumerable:!0,get:o})},e.n=function(t){var i=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(i,"a",i),i},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=123)}([function(t,e,i){e.__esModule=!0,e.default=function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}},function(t,e,i){e.__esModule=!0;var o=i(169),n=function(t){return t&&t.__esModule?t:{default:t}}(o);e.default=function(){function t(t,e){for(var i=0;i2&&void 0!==arguments[2]&&arguments[2];for(var s in t)void 0!==i[s]&&(null===i[s]||"object"!==(0,c.default)(i[s])?n(t,i,s,o):"object"===(0,c.default)(t[s])&&e.fillIfDefined(t[s],i[s],o))},e.extend=function(t,e){for(var i=1;i3&&void 0!==arguments[3]&&arguments[3];if(Array.isArray(o))throw new TypeError("Arrays are not supported by deepExtend");for(var r=0;r3&&void 0!==arguments[3]&&arguments[3];if(Array.isArray(o))throw new TypeError("Arrays are not supported by deepExtend");for(var r in o)if(o.hasOwnProperty(r)&&-1===t.indexOf(r))if(o[r]&&o[r].constructor===Object)void 0===i[r]&&(i[r]={}),i[r].constructor===Object?e.deepExtend(i[r],o[r]):n(i,o,r,s);else if(Array.isArray(o[r])){i[r]=[];for(var a=0;a2&&void 0!==arguments[2]&&arguments[2],s=arguments.length>3&&void 0!==arguments[3]&&arguments[3];for(var r in i)if(i.hasOwnProperty(r)||!0===o)if(i[r]&&i[r].constructor===Object)void 0===t[r]&&(t[r]={}),t[r].constructor===Object?e.deepExtend(t[r],i[r],o):n(t,i,r,s);else if(Array.isArray(i[r])){t[r]=[];for(var a=0;a=0&&(e="DOMMouseScroll"),t.addEventListener(e,i,o)):t.attachEvent("on"+e,i)},e.removeEventListener=function(t,e,i,o){t.removeEventListener?(void 0===o&&(o=!1),"mousewheel"===e&&navigator.userAgent.indexOf("Firefox")>=0&&(e="DOMMouseScroll"),t.removeEventListener(e,i,o)):t.detachEvent("on"+e,i)},e.preventDefault=function(t){t||(t=window.event),t.preventDefault?t.preventDefault():t.returnValue=!1},e.getTarget=function(t){t||(t=window.event);var e;return t.target?e=t.target:t.srcElement&&(e=t.srcElement),void 0!=e.nodeType&&3==e.nodeType&&(e=e.parentNode),e},e.hasParent=function(t,e){for(var i=t;i;){if(i===e)return!0;i=i.parentNode}return!1},e.option={},e.option.asBoolean=function(t,e){return"function"==typeof t&&(t=t()),null!=t?0!=t:e||null},e.option.asNumber=function(t,e){return"function"==typeof t&&(t=t()),null!=t?Number(t)||e||null:e||null},e.option.asString=function(t,e){return"function"==typeof t&&(t=t()),null!=t?String(t):e||null},e.option.asSize=function(t,i){return"function"==typeof t&&(t=t()),e.isString(t)?t:e.isNumber(t)?t+"px":i||null},e.option.asElement=function(t,e){return"function"==typeof t&&(t=t()),t||e||null},e.hexToRGB=function(t){var e=/^#?([a-f\d])([a-f\d])([a-f\d])$/i;t=t.replace(e,function(t,e,i,o){return e+e+i+i+o+o});var i=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(t);return i?{r:parseInt(i[1],16),g:parseInt(i[2],16),b:parseInt(i[3],16)}:null},e.overrideOpacity=function(t,i){var o;return-1!=t.indexOf("rgba")?t:-1!=t.indexOf("rgb")?(o=t.substr(t.indexOf("(")+1).replace(")","").split(","),"rgba("+o[0]+","+o[1]+","+o[2]+","+i+")"):(o=e.hexToRGB(t),null==o?t:"rgba("+o.r+","+o.g+","+o.b+","+i+")")},e.RGBToHex=function(t,e,i){return"#"+((1<<24)+(t<<16)+(e<<8)+i).toString(16).slice(1)},e.parseColor=function(t){var i;if(!0===e.isString(t)){if(!0===e.isValidRGB(t)){var o=t.substr(4).substr(0,t.length-5).split(",").map(function(t){return parseInt(t)});t=e.RGBToHex(o[0],o[1],o[2])}if(!0===e.isValidHex(t)){var n=e.hexToHSV(t),s={h:n.h,s:.8*n.s,v:Math.min(1,1.02*n.v)},r={h:n.h,s:Math.min(1,1.25*n.s),v:.8*n.v},a=e.HSVToHex(r.h,r.s,r.v),h=e.HSVToHex(s.h,s.s,s.v);i={background:t,border:a,highlight:{background:h,border:a},hover:{background:h,border:a}}}else i={background:t,border:t,highlight:{background:t,border:t},hover:{background:t,border:t}}}else i={},i.background=t.background||void 0,i.border=t.border||void 0,e.isString(t.highlight)?i.highlight={border:t.highlight,background:t.highlight}:(i.highlight={},i.highlight.background=t.highlight&&t.highlight.background||void 0,i.highlight.border=t.highlight&&t.highlight.border||void 0),e.isString(t.hover)?i.hover={border:t.hover,background:t.hover}:(i.hover={},i.hover.background=t.hover&&t.hover.background||void 0,i.hover.border=t.hover&&t.hover.border||void 0);return i},e.RGBToHSV=function(t,e,i){t/=255,e/=255,i/=255;var o=Math.min(t,Math.min(e,i)),n=Math.max(t,Math.max(e,i));if(o==n)return{h:0,s:0,v:o};var s=t==o?e-i:i==o?t-e:i-t;return{h:60*((t==o?3:i==o?1:5)-s/(n-o))/360,s:(n-o)/n,v:n}};var v={split:function(t){var e={};return t.split(";").forEach(function(t){if(""!=t.trim()){var i=t.split(":"),o=i[0].trim(),n=i[1].trim();e[o]=n}}),e},join:function(t){return(0,l.default)(t).map(function(e){return e+": "+t[e]}).join("; ")}};e.addCssText=function(t,i){var o=v.split(t.style.cssText),n=v.split(i),s=e.extend(o,n);t.style.cssText=v.join(s)},e.removeCssText=function(t,e){var i=v.split(t.style.cssText),o=v.split(e);for(var n in o)o.hasOwnProperty(n)&&delete i[n];t.style.cssText=v.join(i)},e.HSVToRGB=function(t,e,i){var o,n,s,r=Math.floor(6*t),a=6*t-r,h=i*(1-e),d=i*(1-a*e),l=i*(1-(1-a)*e);switch(r%6){case 0:o=i,n=l,s=h;break;case 1:o=d,n=i,s=h;break;case 2:o=h,n=i,s=l;break;case 3:o=h,n=d,s=i;break;case 4:o=l,n=h,s=i;break;case 5:o=i,n=h,s=d}return{r:Math.floor(255*o),g:Math.floor(255*n),b:Math.floor(255*s)}},e.HSVToHex=function(t,i,o){var n=e.HSVToRGB(t,i,o);return e.RGBToHex(n.r,n.g,n.b)},e.hexToHSV=function(t){var i=e.hexToRGB(t);return e.RGBToHSV(i.r,i.g,i.b)},e.isValidHex=function(t){return/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(t)},e.isValidRGB=function(t){return t=t.replace(" ",""),/rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/i.test(t)},e.isValidRGBA=function(t){return t=t.replace(" ",""),/rgba\((\d{1,3}),(\d{1,3}),(\d{1,3}),(.{1,3})\)/i.test(t)},e.selectiveBridgeObject=function(t,i){if(null!==i&&"object"===(void 0===i?"undefined":(0,c.default)(i))){for(var o=(0,h.default)(i),n=0;n0&&e(o,t[n-1])<0;n--)t[n]=t[n-1];t[n]=o}return t},e.mergeOptions=function(t,e,i){var o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},n=function(t){return null!==t&&void 0!==t},s=function(t){return null!==t&&"object"===(void 0===t?"undefined":(0,c.default)(t))};if(!s(t))throw new Error("Parameter mergeTarget must be an object");if(!s(e))throw new Error("Parameter options must be an object");if(!n(i))throw new Error("Parameter option must have a value");if(!s(o))throw new Error("Parameter globalOptions must be an object");var r=e[i],a=s(o)&&!function(t){for(var e in t)if(t.hasOwnProperty(e))return!1;return!0}(o),d=a?o[i]:void 0,l=d?d.enabled:void 0;if(void 0!==r){if("boolean"==typeof r)return s(t[i])||(t[i]={}),void(t[i].enabled=r);if(null===r&&!s(t[i])){if(!n(d))return;t[i]=(0,h.default)(d)}if(s(r)){var u=!0;void 0!==r.enabled?u=r.enabled:void 0!==l&&(u=d.enabled),function(t,e,i){s(t[i])||(t[i]={});var o=e[i],n=t[i];for(var r in o)o.hasOwnProperty(r)&&(n[r]=o[r])}(t,e,i),t[i].enabled=u}}},e.binarySearchCustom=function(t,e,i,o){for(var n=0,s=0,r=t.length-1;s<=r&&n<1e4;){var a=Math.floor((s+r)/2),h=t[a],d=void 0===o?h[i]:h[i][o],l=e(d);if(0==l)return a;-1==l?s=a+1:r=a-1,n++}return-1},e.binarySearchValue=function(t,e,i,o,n){var s,r,a,h,d=0,l=0,u=t.length-1;for(n=void 0!=n?n:function(t,e){return t==e?0:t0)return"before"==o?Math.max(0,h-1):h;if(n(r,e)<0&&n(a,e)>0)return"before"==o?h:Math.min(t.length-1,h+1);n(r,e)<0?l=h+1:u=h-1,d++}return-1},e.easingFunctions={linear:function(t){return t},easeInQuad:function(t){return t*t},easeOutQuad:function(t){return t*(2-t)},easeInOutQuad:function(t){return t<.5?2*t*t:(4-2*t)*t-1},easeInCubic:function(t){return t*t*t},easeOutCubic:function(t){return--t*t*t+1},easeInOutCubic:function(t){return t<.5?4*t*t*t:(t-1)*(2*t-2)*(2*t-2)+1},easeInQuart:function(t){return t*t*t*t},easeOutQuart:function(t){return 1- --t*t*t*t},easeInOutQuart:function(t){return t<.5?8*t*t*t*t:1-8*--t*t*t*t},easeInQuint:function(t){return t*t*t*t*t},easeOutQuint:function(t){return 1+--t*t*t*t*t},easeInOutQuint:function(t){return t<.5?16*t*t*t*t*t:1+16*--t*t*t*t*t}},e.getScrollBarWidth=function(){var t=document.createElement("p");t.style.width="100%",t.style.height="200px";var e=document.createElement("div");e.style.position="absolute",e.style.top="0px",e.style.left="0px",e.style.visibility="hidden",e.style.width="200px",e.style.height="150px",e.style.overflow="hidden",e.appendChild(t),document.body.appendChild(e);var i=t.offsetWidth;e.style.overflow="scroll";var o=t.offsetWidth;return i==o&&(o=e.clientWidth),document.body.removeChild(e),i-o},e.topMost=function(t,e){var i=void 0;Array.isArray(e)||(e=[e]);var o=!0,n=!1,s=void 0;try{for(var a,h=(0,r.default)(t);!(o=(a=h.next()).done);o=!0){var d=a.value;if(d){i=d[e[0]];for(var l=1;ln?1:or)&&(s=h,r=d)}return s},n.prototype.min=function(t){var e,i,o=this._data,n=(0,l.default)(o),s=null,r=null;for(e=0,i=n.length;e0?(o=e[t].redundant[0],e[t].redundant.shift()):(o=document.createElementNS("http://www.w3.org/2000/svg",t),i.appendChild(o)):(o=document.createElementNS("http://www.w3.org/2000/svg",t),e[t]={used:[],redundant:[]},i.appendChild(o)),e[t].used.push(o),o},e.getDOMElement=function(t,e,i,o){var n;return e.hasOwnProperty(t)?e[t].redundant.length>0?(n=e[t].redundant[0],e[t].redundant.shift()):(n=document.createElement(t),void 0!==o?i.insertBefore(n,o):i.appendChild(n)):(n=document.createElement(t),e[t]={used:[],redundant:[]},void 0!==o?i.insertBefore(n,o):i.appendChild(n)),e[t].used.push(n),n},e.drawPoint=function(t,i,o,n,s,r){var a;if("circle"==o.style?(a=e.getSVGElement("circle",n,s),a.setAttributeNS(null,"cx",t),a.setAttributeNS(null,"cy",i),a.setAttributeNS(null,"r",.5*o.size)):(a=e.getSVGElement("rect",n,s),a.setAttributeNS(null,"x",t-.5*o.size),a.setAttributeNS(null,"y",i-.5*o.size),a.setAttributeNS(null,"width",o.size),a.setAttributeNS(null,"height",o.size)),void 0!==o.styles&&a.setAttributeNS(null,"style",o.styles),a.setAttributeNS(null,"class",o.className+" vis-point"),r){var h=e.getSVGElement("text",n,s);r.xOffset&&(t+=r.xOffset),r.yOffset&&(i+=r.yOffset),r.content&&(h.textContent=r.content),r.className&&h.setAttributeNS(null,"class",r.className+" vis-label"),h.setAttributeNS(null,"x",t),h.setAttributeNS(null,"y",i)}return a},e.drawBar=function(t,i,o,n,s,r,a,h){if(0!=n){n<0&&(n*=-1,i-=n);var d=e.getSVGElement("rect",r,a);d.setAttributeNS(null,"x",t-.5*o),d.setAttributeNS(null,"y",i),d.setAttributeNS(null,"width",o),d.setAttributeNS(null,"height",n),d.setAttributeNS(null,"class",s),h&&d.setAttributeNS(null,"style",h)}}},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0}),e.printStyle=void 0;var n=i(19),s=o(n),r=i(6),a=o(r),h=i(8),d=o(h),l=i(0),u=o(l),c=i(1),p=o(c),f=i(2),m=!1,v=void 0,g="background: #FFeeee; color: #dd0000",y=function(){function t(){(0,u.default)(this,t)}return(0,p.default)(t,null,[{key:"validate",value:function(e,i,o){m=!1,v=i;var n=i;return void 0!==o&&(n=i[o]),t.parse(e,n,[]),m}},{key:"parse",value:function(e,i,o){for(var n in e)e.hasOwnProperty(n)&&t.check(n,e,i,o)}},{key:"check",value:function(e,i,o,n){if(void 0===o[e]&&void 0===o.__any__)return void t.getSuggestion(e,o,n);var s=e,r=!0;void 0===o[e]&&void 0!==o.__any__&&(s="__any__",r="object"===t.getType(i[e]));var a=o[s];r&&void 0!==a.__type__&&(a=a.__type__),t.checkFields(e,i,o,s,a,n)}},{key:"checkFields",value:function(e,i,o,n,s,r){var a=function(i){console.log("%c"+i+t.printLocation(r,e),g)},h=t.getType(i[e]),l=s[h];void 0!==l?"array"===t.getType(l)&&-1===l.indexOf(i[e])?(a('Invalid option detected in "'+e+'". Allowed values are:'+t.print(l)+' not "'+i[e]+'". '),m=!0):"object"===h&&"__any__"!==n&&(r=f.copyAndExtendArray(r,e),t.parse(i[e],o[n],r)):void 0===s.any&&(a('Invalid type received for "'+e+'". Expected: '+t.print((0,d.default)(s))+". Received ["+h+'] "'+i[e]+'"'),m=!0)}},{key:"getType",value:function(t){var e=void 0===t?"undefined":(0,a.default)(t);return"object"===e?null===t?"null":t instanceof Boolean?"boolean":t instanceof Number?"number":t instanceof String?"string":Array.isArray(t)?"array":t instanceof Date?"date":void 0!==t.nodeType?"dom":!0===t._isAMomentObject?"moment":"object":"number"===e?"number":"boolean"===e?"boolean":"string"===e?"string":void 0===e?"undefined":e}},{key:"getSuggestion",value:function(e,i,o){var n=t.findInOptions(e,i,o,!1),s=t.findInOptions(e,v,[],!0),r=void 0 ;r=void 0!==n.indexMatch?" in "+t.printLocation(n.path,e,"")+'Perhaps it was incomplete? Did you mean: "'+n.indexMatch+'"?\n\n':s.distance<=4&&n.distance>s.distance?" in "+t.printLocation(n.path,e,"")+"Perhaps it was misplaced? Matching option found at: "+t.printLocation(s.path,s.closestMatch,""):n.distance<=8?'. Did you mean "'+n.closestMatch+'"?'+t.printLocation(n.path,e):". Did you mean one of these: "+t.print((0,d.default)(i))+t.printLocation(o,e),console.log('%cUnknown option detected: "'+e+'"'+r,g),m=!0}},{key:"findInOptions",value:function(e,i,o){var n=arguments.length>3&&void 0!==arguments[3]&&arguments[3],s=1e9,r="",a=[],h=e.toLowerCase(),d=void 0;for(var l in i){var u=void 0;if(void 0!==i[l].__type__&&!0===n){var c=t.findInOptions(e,i[l],f.copyAndExtendArray(o,l));s>c.distance&&(r=c.closestMatch,a=c.path,s=c.distance,d=c.indexMatch)}else-1!==l.toLowerCase().indexOf(h)&&(d=l),u=t.levenshteinDistance(e,l),s>u&&(r=l,a=f.copyArray(o),s=u)}return{closestMatch:r,path:a,distance:s,indexMatch:d}}},{key:"printLocation",value:function(t,e){for(var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"Problem value found at: \n",o="\n\n"+i+"options = {\n",n=0;n0&&(this.enableBorderDashes(t,e),t.stroke(),this.disableBorderDashes(t,e)),t.restore()}},{key:"performFill",value:function(t,e){this.enableShadow(t,e),t.fill(),this.disableShadow(t,e),this.performStroke(t,e)}},{key:"_addBoundingBoxMargin",value:function(t){this.boundingBox.left-=t,this.boundingBox.top-=t,this.boundingBox.bottom+=t,this.boundingBox.right+=t}},{key:"_updateBoundingBox",value:function(t,e,i,o,n){void 0!==i&&this.resize(i,o,n),this.left=t-this.width/2,this.top=e-this.height/2,this.boundingBox.left=this.left,this.boundingBox.top=this.top,this.boundingBox.bottom=this.top+this.height,this.boundingBox.right=this.left+this.width}},{key:"updateBoundingBox",value:function(t,e,i,o,n){this._updateBoundingBox(t,e,i,o,n)}},{key:"getDimensionsFromLabel",value:function(t,e,i){this.textSize=this.labelModule.getTextSize(t,e,i);var o=this.textSize.width,n=this.textSize.height;return 0===o&&(o=14,n=14),{width:o,height:n}}}]),t}();e.default=l},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(23),m=o(f),v=function(t){function e(t,i,o){return(0,a.default)(this,e),(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o))}return(0,p.default)(e,t),(0,d.default)(e,[{key:"resize",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.selected,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:this.hover,o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{size:this.options.size};if(this.needsRefresh(e,i)){this.labelModule.getTextSize(t,e,i);var n=2*o.size;this.width=n,this.height=n,this.radius=.5*this.width}}},{key:"_drawShape",value:function(t,e,i,o,n,s,r,a){if(this.resize(t,s,r,a),this.left=o-this.width/2,this.top=n-this.height/2,this.initContextForDraw(t,a),t[e](o,n,a.size),this.performFill(t,a),void 0!==this.options.label){this.labelModule.calculateLabelSize(t,s,r,o,n,"hanging");var h=n+.5*this.height+.5*this.labelModule.size.height;this.labelModule.draw(t,o,h,s,r,"hanging")}this.updateBoundingBox(o,n)}},{key:"updateBoundingBox",value:function(t,e){this.boundingBox.top=e-this.options.size,this.boundingBox.left=t-this.options.size,this.boundingBox.right=t+this.options.size,this.boundingBox.bottom=e+this.options.size,void 0!==this.options.label&&this.labelModule.size.width>0&&(this.boundingBox.left=Math.min(this.boundingBox.left,this.labelModule.size.left),this.boundingBox.right=Math.max(this.boundingBox.right,this.labelModule.size.left+this.labelModule.size.width),this.boundingBox.bottom=Math.max(this.boundingBox.bottom,this.boundingBox.bottom+this.labelModule.size.height))}}]),e}(m.default);e.default=v},function(t,e,i){var o=i(78),n=i(51);t.exports=function(t){return o(n(t))}},function(t,e,i){var o=i(20),n=i(39);t.exports=i(21)?function(t,e,i){return o.f(t,e,n(1,i))}:function(t,e,i){return t[e]=i,t}},function(t,e,i){var o=i(32);t.exports=function(t){if(!o(t))throw TypeError(t+" is not an object!");return t}},function(t,e){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,e,i){t.exports={default:i(138),__esModule:!0}},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}e.__esModule=!0;var n=i(188),s=o(n),r=i(77),a=o(r);e.default=function(){function t(t,e){var i=[],o=!0,n=!1,s=void 0;try{for(var r,h=(0,a.default)(t);!(o=(r=h.next()).done)&&(i.push(r.value),!e||i.length!==e);o=!0);}catch(t){n=!0,s=t}finally{try{!o&&h.return&&h.return()}finally{if(n)throw s}}return i}return function(e,i){if(Array.isArray(e))return e;if((0,s.default)(Object(e)))return t(e,i);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}()},function(t,e){t.exports={}},function(t,e){t.exports=function(t){return"object"==typeof t?null!==t:"function"==typeof t}},function(t,e,i){var o=i(84),n=i(58);t.exports=Object.keys||function(t){return o(t,n)}},function(t,e,i){function o(t,e,i){this.x=void 0!==t?t:0,this.y=void 0!==e?e:0,this.z=void 0!==i?i:0}o.subtract=function(t,e){var i=new o;return i.x=t.x-e.x,i.y=t.y-e.y,i.z=t.z-e.z,i},o.add=function(t,e){var i=new o;return i.x=t.x+e.x,i.y=t.y+e.y,i.z=t.z+e.z,i},o.avg=function(t,e){return new o((t.x+e.x)/2,(t.y+e.y)/2,(t.z+e.z)/2)},o.crossProduct=function(t,e){var i=new o;return i.x=t.y*e.z-t.z*e.y,i.y=t.z*e.x-t.x*e.z,i.z=t.x*e.y-t.y*e.x,i},o.prototype.length=function(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)},t.exports=o},function(t,e,i){var o,n,s;!function(i,r){n=[],o=r,void 0!==(s="function"==typeof o?o.apply(e,n):o)&&(t.exports=s)}(0,function(){function t(t){var e,i=t&&t.preventDefault||!1,o=t&&t.container||window,n={},s={keydown:{},keyup:{}},r={};for(e=97;e<=122;e++)r[String.fromCharCode(e)]={code:e-97+65,shift:!1};for(e=65;e<=90;e++)r[String.fromCharCode(e)]={code:e,shift:!0};for(e=0;e<=9;e++)r[""+e]={code:48+e,shift:!1};for(e=1;e<=12;e++)r["F"+e]={code:111+e,shift:!1};for(e=0;e<=9;e++)r["num"+e]={code:96+e,shift:!1};r["num*"]={code:106,shift:!1},r["num+"]={code:107,shift:!1},r["num-"]={code:109,shift:!1},r["num/"]={code:111,shift:!1},r["num."]={code:110,shift:!1},r.left={code:37,shift:!1},r.up={code:38,shift:!1},r.right={code:39,shift:!1},r.down={code:40,shift:!1},r.space={code:32,shift:!1},r.enter={code:13,shift:!1},r.shift={code:16,shift:void 0},r.esc={code:27,shift:!1},r.backspace={code:8,shift:!1},r.tab={code:9,shift:!1},r.ctrl={code:17,shift:!1},r.alt={code:18,shift:!1},r.delete={code:46,shift:!1},r.pageup={code:33,shift:!1},r.pagedown={code:34,shift:!1},r["="]={code:187,shift:!1},r["-"]={code:189,shift:!1},r["]"]={code:221,shift:!1},r["["]={code:219,shift:!1};var a=function(t){d(t,"keydown")},h=function(t){d(t,"keyup")},d=function(t,e){if(void 0!==s[e][t.keyCode]){for(var o=s[e][t.keyCode],n=0;n=4*a){var c=0,p=s.clone();switch(o[h].repeat){case"daily":d.day()!=l.day()&&(c=1),d.dayOfYear(n.dayOfYear()),d.year(n.year()),d.subtract(7,"days"),l.dayOfYear(n.dayOfYear()),l.year(n.year()),l.subtract(7-c,"days"),p.add(1,"weeks");break;case"weekly":var f=l.diff(d,"days"),m=d.day();d.date(n.date()),d.month(n.month()),d.year(n.year()),l=d.clone(),d.day(m),l.day(m),l.add(f,"days"),d.subtract(1,"weeks"),l.subtract(1,"weeks"),p.add(1,"weeks");break;case"monthly":d.month()!=l.month()&&(c=1),d.month(n.month()),d.year(n.year()),d.subtract(1,"months"),l.month(n.month()),l.year(n.year()),l.subtract(1,"months"),l.add(c,"months"),p.add(1,"months");break;case"yearly":d.year()!=l.year()&&(c=1),d.year(n.year()),d.subtract(1,"years"),l.year(n.year()),l.subtract(1,"years"),l.add(c,"years"),p.add(1,"years");break;default:return void console.log("Wrong repeat format, allowed are: daily, weekly, monthly, yearly. Given:",o[h].repeat)}for(;d=e[o].start&&e[n].end<=e[o].end?e[n].remove=!0:e[n].start>=e[o].start&&e[n].start<=e[o].end?(e[o].end=e[n].end,e[n].remove=!0):e[n].end>=e[o].start&&e[n].end<=e[o].end&&(e[o].start=e[n].start,e[n].remove=!0));for(o=0;o=r&&nt.range.end){var h={start:t.range.start,end:i};return i=e.correctTimeForHidden(t.options.moment,t.body.hiddenDates,h,i),n=t.range.conversion(o,r),(i.valueOf()-n.offset)*n.scale}return i=e.correctTimeForHidden(t.options.moment,t.body.hiddenDates,t.range,i),n=t.range.conversion(o,r),(i.valueOf()-n.offset)*n.scale},e.toTime=function(t,i,o){if(0==t.body.hiddenDates.length){var n=t.range.conversion(o);return new Date(i/n.scale+n.offset)}var s=e.getHiddenDurationBetween(t.body.hiddenDates,t.range.start,t.range.end),r=t.range.end-t.range.start-s,a=r*i/o,h=e.getAccumulatedHiddenDuration(t.body.hiddenDates,t.range,a);return new Date(h+a+t.range.start)},e.getHiddenDurationBetween=function(t,e,i){for(var o=0,n=0;n=e&&r=e&&r<=i&&(o+=r-s)}return o},e.correctTimeForHidden=function(t,i,o,n){return n=t(n).toDate().valueOf(),n-=e.getHiddenDurationBefore(t,i,o,n)},e.getHiddenDurationBefore=function(t,e,i,o){var n=0;o=t(o).toDate().valueOf();for(var s=0;s=i.start&&a=a&&(n+=a-r)}return n},e.getAccumulatedHiddenDuration=function(t,e,i){for(var o=0,n=0,s=e.start,r=0;r=e.start&&h=i)break;o+=h-a}}return o},e.snapAwayFromHidden=function(t,i,o,n){var s=e.isHidden(i,t);return 1==s.hidden?o<0?1==n?s.startDate-(s.endDate-i)-1:s.startDate-1:1==n?s.endDate+(i-s.startDate)+1:s.endDate+1:i},e.isHidden=function(t,e){for(var i=0;i=o&&t0){var e=[];if(Array.isArray(this.options.dataAttributes))e=this.options.dataAttributes;else{if("all"!=this.options.dataAttributes)return;e=(0,h.default)(this.data)}for(var i=0;ithis.max&&this.flush(),clearTimeout(this._timeout),this.queue.length>0&&"number"==typeof this.delay){var t=this;this._timeout=setTimeout(function(){t.flush()},this.delay)}},o.prototype.flush=function(){for(;this._queue.length>0;){var t=this._queue.shift();t.fn.apply(t.context||t.fn,t.args||[])}},t.exports=o},function(t,e){function i(t){if(t)return o(t)}function o(t){for(var e in i.prototype)t[e]=i.prototype[e];return t}t.exports=i,i.prototype.on=i.prototype.addEventListener=function(t,e){return this._callbacks=this._callbacks||{},(this._callbacks[t]=this._callbacks[t]||[]).push(e),this},i.prototype.once=function(t,e){function i(){o.off(t,i),e.apply(this,arguments)}var o=this;return this._callbacks=this._callbacks||{},i.fn=e,this.on(t,i),this},i.prototype.off=i.prototype.removeListener=i.prototype.removeAllListeners=i.prototype.removeEventListener=function(t,e){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var i=this._callbacks[t];if(!i)return this;if(1==arguments.length)return delete this._callbacks[t],this;for(var o,n=0;n=.4*v}if(this.options.showMinorLabels&&m){var k=this._repaintMinorText(c,y,t,b);k.style.width=_+"px"}f&&this.options.showMajorLabels?(c>0&&(void 0==w&&(w=c),k=this._repaintMajorText(c,s.getLabelMajor(),t,b)),g=this._repaintMajorLine(c,_,t,b)):m?g=this._repaintMinorLine(c,_,t,b):g&&(g.style.width=parseInt(g.style.width)+_+"px")}if(1e3!==x||u||(console.warn("Something is wrong with the Timeline scale. Limited drawing of grid lines to 1000 lines."),u=!0),this.options.showMajorLabels){var S=this.body.util.toTime(0),D=s.getLabelMajor(S),M=D.length*(this.props.majorCharWidth||10)+10;(void 0==w||Mt.left&&this.shape.topt.top}},{key:"isBoundingBoxOverlappingWith",value:function(t){return this.shape.boundingBox.leftt.left&&this.shape.boundingBox.topt.top}}],[{key:"updateGroupOptions",value:function(t,e,i){if(void 0!==i){var o=t.group;if(void 0!==e&&void 0!==e.group&&o!==e.group)throw new Error("updateGroupOptions: group values in options don't match.");if("number"==typeof o||"string"==typeof o&&""!=o){var n=i.get(o);h.selectiveNotDeepExtend(["font"],t,n),t.color=h.parseColor(t.color)}}}},{key:"parseOptions",value:function(e,i){var o=arguments.length>2&&void 0!==arguments[2]&&arguments[2],n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},s=arguments[4],r=["color","fixed","shadow"];if(h.selectiveNotDeepExtend(r,e,i,o),t.checkMass(i),h.mergeOptions(e,i,"shadow",n),void 0!==i.color&&null!==i.color){var a=h.parseColor(i.color);h.fillIfDefined(e.color,a)}else!0===o&&null===i.color&&(e.color=h.bridgeObject(n.color));void 0!==i.fixed&&null!==i.fixed&&("boolean"==typeof i.fixed?(e.fixed.x=i.fixed,e.fixed.y=i.fixed):(void 0!==i.fixed.x&&"boolean"==typeof i.fixed.x&&(e.fixed.x=i.fixed.x),void 0!==i.fixed.y&&"boolean"==typeof i.fixed.y&&(e.fixed.y=i.fixed.y))),!0===o&&null===i.font&&(e.font=h.bridgeObject(n.font)),t.updateGroupOptions(e,i,s),void 0!==i.scaling&&h.mergeOptions(e.scaling,i.scaling,"label",n.scaling)}},{key:"checkMass",value:function(t,e){if(void 0!==t.mass&&t.mass<=0){var i="";void 0!==e&&(i=" in node id: "+e),console.log("%cNegative or zero mass disallowed"+i+", setting mass to 1.",C),t.mass=1}}}]),t}();e.default=O},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(6),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(2),u=function(){function t(){(0,a.default)(this,t)}return(0,d.default)(t,null,[{key:"choosify",value:function(t,e){var i=["node","edge","label"],o=!0,n=l.topMost(e,"chosen");if("boolean"==typeof n)o=n;else if("object"===(void 0===n?"undefined":(0,s.default)(n))){if(-1===i.indexOf(t))throw new Error("choosify: subOption '"+t+"' should be one of '"+i.join("', '")+"'");var r=l.topMost(e,["chosen",t]);"boolean"!=typeof r&&"function"!=typeof r||(o=r)}return o}},{key:"pointInRect",value:function(t,e,i){if(t.width<=0||t.height<=0)return!1;if(void 0!==i){var o={x:e.x-i.x,y:e.y-i.y};if(0!==i.angle){var n=-i.angle;e={x:Math.cos(n)*o.x-Math.sin(n)*o.y,y:Math.sin(n)*o.x+Math.cos(n)*o.y}}else e=o}var s=t.x+t.width,r=t.y+t.width;return t.lefte.x&&t.tope.y}},{key:"isValidLabel",value:function(t){return"string"==typeof t&&""!==t}}]),t}();e.default=u},function(t,e,i){i(125);for(var o=i(18),n=i(26),s=i(31),r=i(13)("toStringTag"),a="CSSRuleList,CSSStyleDeclaration,CSSValueList,ClientRectList,DOMRectList,DOMStringList,DOMTokenList,DataTransferItemList,FileList,HTMLAllCollection,HTMLCollection,HTMLFormElement,HTMLSelectElement,MediaList,MimeTypeArray,NamedNodeMap,NodeList,PaintRequestList,Plugin,PluginArray,SVGLengthList,SVGNumberList,SVGPathSegList,SVGPointList,SVGStringList,SVGTransformList,SourceBufferList,StyleSheetList,TextTrackCueList,TextTrackList,TouchList".split(","),h=0;hdocument.F=Object<\/script>"),t.close(),h=t.F;o--;)delete h.prototype[s[o]];return h()};t.exports=Object.create||function(t,e){var i;return null!==t?(a.prototype=o(t),i=new a,a.prototype=null,i[r]=t):i=h(),void 0===e?i:n(i,e)}},function(t,e){var i=Math.ceil,o=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?o:i)(t)}},function(t,e,i){var o=i(57)("keys"),n=i(40);t.exports=function(t){return o[t]||(o[t]=n(t))}},function(t,e,i){var o=i(18),n=o["__core-js_shared__"]||(o["__core-js_shared__"]={});t.exports=function(t){return n[t]||(n[t]={})}},function(t,e){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(t,e,i){var o=i(20).f,n=i(22),s=i(13)("toStringTag");t.exports=function(t,e,i){t&&!n(t=i?t:t.prototype,s)&&o(t,s,{configurable:!0,value:e})}},function(t,e,i){var o=i(135)(!0);i(79)(String,"String",function(t){this._t=String(t),this._i=0},function(){var t,e=this._t,i=this._i;return i>=e.length?{value:void 0,done:!0}:(t=o(e,i),this._i+=t.length,{value:t,done:!1})})},function(t,e,i){e.f=i(13)},function(t,e,i){var o=i(18),n=i(7),s=i(52),r=i(61),a=i(20).f;t.exports=function(t){var e=n.Symbol||(n.Symbol=s?{}:o.Symbol||{});"_"==t.charAt(0)||t in e||a(e,t,{value:r.f(t)})}},function(t,e){e.f=Object.getOwnPropertySymbols},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}function n(t,e){var i=p().hours(0).minutes(0).seconds(0).milliseconds(0),o=i.clone().add(-3,"days").valueOf(),n=i.clone().add(3,"days").valueOf();this.millisecondsPerPixelCache=void 0,void 0===e?(this.start=o,this.end=n):(this.start=e.start||o,this.end=e.end||n),this.rolling=!1,this.body=t,this.deltaDifference=0,this.scaleOffset=0,this.startToFront=!1,this.endToFront=!0,this.defaultOptions={rtl:!1,start:null,end:null,moment:p,direction:"horizontal",moveable:!0,zoomable:!0,min:null,max:null,zoomMin:10,zoomMax:31536e10,rollingMode:{follow:!1,offset:.5}},this.options=c.extend({},this.defaultOptions),this.props={touch:{}},this.animationTimer=null,this.body.emitter.on("panstart",this._onDragStart.bind(this)),this.body.emitter.on("panmove",this._onDrag.bind(this)),this.body.emitter.on("panend",this._onDragEnd.bind(this)),this.body.emitter.on("mousewheel",this._onMouseWheel.bind(this)),this.body.emitter.on("touch",this._onTouch.bind(this)),this.body.emitter.on("pinch",this._onPinch.bind(this)),this.body.dom.rollingModeBtn.addEventListener("click",this.startRolling.bind(this)),this.setOptions(e)}function s(t){if("horizontal"!=t&&"vertical"!=t)throw new TypeError('Unknown direction "'+t+'". Choose "horizontal" or "vertical".')}var r=i(8),a=o(r),h=i(19),d=o(h),l=i(6),u=o(l),c=i(2),p=i(9),f=i(16),m=i(36);n.prototype=new f,n.prototype.setOptions=function(t){if(t){var e=["animation","direction","min","max","zoomMin","zoomMax","moveable","zoomable","moment","activate","hiddenDates","zoomKey","rtl","showCurrentTime","rollingMode","horizontalScroll"];c.selectiveExtend(e,this.options,t),t.rollingMode&&t.rollingMode.follow&&this.startRolling(),("start"in t||"end"in t)&&this.setRange(t.start,t.end)}},n.prototype.startRolling=function(){function t(){e.stopRolling(),e.rolling=!0;var i=e.end-e.start,o=c.convert(new Date,"Date").valueOf(),n=o-i*e.options.rollingMode.offset,s=o+i*(1-e.options.rollingMode.offset),r={animation:!1};e.setRange(n,s,r),i=1/e.conversion(e.body.domProps.center.width).scale/10,i<30&&(i=30),i>1e3&&(i=1e3),e.body.dom.rollingModeBtn.style.visibility="hidden",e.currentTimeTimer=setTimeout(t,i)}var e=this;t()},n.prototype.stopRolling=function(){void 0!==this.currentTimeTimer&&(clearTimeout(this.currentTimeTimer),this.rolling=!1,this.body.dom.rollingModeBtn.style.visibility="visible")},n.prototype.setRange=function(t,e,i,o,n){i||(i={}),!0!==i.byUser&&(i.byUser=!1);var s=this,r=void 0!=t?c.convert(t,"Date").valueOf():null,h=void 0!=e?c.convert(e,"Date").valueOf():null;if(this._cancelAnimation(),this.millisecondsPerPixelCache=void 0,i.animation){var l=this.start,p=this.end,f="object"===(0,u.default)(i.animation)&&"duration"in i.animation?i.animation.duration:500,v="object"===(0,u.default)(i.animation)&&"easingFunction"in i.animation?i.animation.easingFunction:"easeInOutQuad",g=c.easingFunctions[v];if(!g)throw new Error("Unknown easing function "+(0,d.default)(v)+". Choose from: "+(0,a.default)(c.easingFunctions).join(", "));var y=(new Date).valueOf(),b=!1;return function t(){if(!s.props.touch.dragging){var e=(new Date).valueOf(),a=e-y,d=g(a/f),u=a>f,c=u||null===r?r:l+(r-l)*d,v=u||null===h?h:p+(h-p)*d;_=s._applyRange(c,v),m.updateHiddenDates(s.options.moment,s.body,s.options.hiddenDates),b=b||_;var w={start:new Date(s.start),end:new Date(s.end),byUser:i.byUser,event:i.event};if(n&&n(d,_,u),_&&s.body.emitter.emit("rangechange",w),u){if(b&&(s.body.emitter.emit("rangechanged",w),o))return o()}else s.animationTimer=setTimeout(t,20)}}()}var _=this._applyRange(r,h);if(m.updateHiddenDates(this.options.moment,this.body,this.options.hiddenDates),_){var w={start:new Date(this.start),end:new Date(this.end),byUser:i.byUser,event:i.event};if(this.body.emitter.emit("rangechange",w),clearTimeout(s.timeoutID),s.timeoutID=setTimeout(function(){s.body.emitter.emit("rangechanged",w)},200),o)return o()}},n.prototype.getMillisecondsPerPixel=function(){return void 0===this.millisecondsPerPixelCache&&(this.millisecondsPerPixelCache=(this.end-this.start)/this.body.dom.center.clientWidth),this.millisecondsPerPixelCache},n.prototype._cancelAnimation=function(){this.animationTimer&&(clearTimeout(this.animationTimer),this.animationTimer=null)},n.prototype._applyRange=function(t,e){var i,o=null!=t?c.convert(t,"Date").valueOf():this.start,n=null!=e?c.convert(e,"Date").valueOf():this.end,s=null!=this.options.max?c.convert(this.options.max,"Date").valueOf():null,r=null!=this.options.min?c.convert(this.options.min,"Date").valueOf():null;if(isNaN(o)||null===o)throw new Error('Invalid start "'+t+'"');if(isNaN(n)||null===n)throw new Error('Invalid end "'+e+'"');if(ns&&(n=s)),null!==s&&n>s&&(i=n-s,o-=i,n-=i,null!=r&&o=this.start-.5&&n<=this.end?(o=this.start,n=this.end):(i=a-(n-o),o-=i/2,n+=i/2)}}if(null!==this.options.zoomMax){var h=parseFloat(this.options.zoomMax);h<0&&(h=0),n-o>h&&(this.end-this.start===h&&othis.end?(o=this.start,n=this.end):(i=n-o-h,o+=i/2,n-=i/2))}var d=this.start!=o||this.end!=n;return o>=this.start&&o<=this.end||n>=this.start&&n<=this.end||this.start>=o&&this.start<=n||this.end>=o&&this.end<=n||this.body.emitter.emit("checkRangedItems"),this.start=o,this.end=n,d},n.prototype.getRange=function(){return{start:this.start,end:this.end}},n.prototype.conversion=function(t,e){return n.conversion(this.start,this.end,t,e)},n.conversion=function(t,e,i,o){return void 0===o&&(o=0),0!=i&&e-t!=0?{offset:t,scale:i/(e-t-o)}:{offset:0,scale:1}},n.prototype._onDragStart=function(t){this.deltaDifference=0,this.previousDelta=0,this.options.moveable&&this._isInsideRange(t)&&this.props.touch.allowDragging&&(this.stopRolling(),this.props.touch.start=this.start,this.props.touch.end=this.end,this.props.touch.dragging=!0,this.body.dom.root&&(this.body.dom.root.style.cursor="move"))},n.prototype._onDrag=function(t){if(t&&this.props.touch.dragging&&this.options.moveable&&this.props.touch.allowDragging){var e=this.options.direction;s(e);var i="horizontal"==e?t.deltaX:t.deltaY;i-=this.deltaDifference;var o=this.props.touch.end-this.props.touch.start;o-=m.getHiddenDurationBetween(this.body.hiddenDates,this.start,this.end);var n,r="horizontal"==e?this.body.domProps.center.width:this.body.domProps.center.height;n=this.options.rtl?i/r*o:-i/r*o;var a=this.props.touch.start+n,h=this.props.touch.end+n,d=m.snapAwayFromHidden(this.body.hiddenDates,a,this.previousDelta-i,!0),l=m.snapAwayFromHidden(this.body.hiddenDates,h,this.previousDelta-i,!0);if(d!=a||l!=h)return this.deltaDifference+=i,this.props.touch.start=d,this.props.touch.end=l,void this._onDrag(t);this.previousDelta=i,this._applyRange(a,h);var u=new Date(this.start),c=new Date(this.end);this.body.emitter.emit("rangechange",{start:u,end:c,byUser:!0,event:t}),this.body.emitter.emit("panmove")}},n.prototype._onDragEnd=function(t){this.props.touch.dragging&&this.options.moveable&&this.props.touch.allowDragging&&(this.props.touch.dragging=!1,this.body.dom.root&&(this.body.dom.root.style.cursor="auto"),this.body.emitter.emit("rangechanged",{start:new Date(this.start),end:new Date(this.end),byUser:!0,event:t}))},n.prototype._onMouseWheel=function(t){var e=0;if(t.wheelDelta?e=t.wheelDelta/120:t.detail&&(e=-t.detail/3),!(this.options.zoomKey&&!t[this.options.zoomKey]&&this.options.zoomable||!this.options.zoomable&&this.options.moveable)&&this.options.zoomable&&this.options.moveable&&this._isInsideRange(t)&&e){var i;i=e<0?1-e/5:1/(1+e/5);var o;if(this.rolling)o=this.start+(this.end-this.start)*this.options.rollingMode.offset;else{var n=this.getPointer({x:t.clientX,y:t.clientY},this.body.dom.center);o=this._pointerToDate(n)}this.zoom(i,o,e,t),t.preventDefault()}},n.prototype._onTouch=function(t){this.props.touch.start=this.start,this.props.touch.end=this.end,this.props.touch.allowDragging=!0,this.props.touch.center=null,this.scaleOffset=0,this.deltaDifference=0,c.preventDefault(t)},n.prototype._onPinch=function(t){if(this.options.zoomable&&this.options.moveable){c.preventDefault(t),this.props.touch.allowDragging=!1,this.props.touch.center||(this.props.touch.center=this.getPointer(t.center,this.body.dom.center)),this.stopRolling();var e=1/(t.scale+this.scaleOffset),i=this._pointerToDate(this.props.touch.center),o=m.getHiddenDurationBetween(this.body.hiddenDates,this.start,this.end),n=m.getHiddenDurationBefore(this.options.moment,this.body.hiddenDates,this,i),s=o-n,r=i-n+(this.props.touch.start-(i-n))*e,a=i+s+(this.props.touch.end-(i+s))*e;this.startToFront=1-e<=0,this.endToFront=e-1<=0;var h=m.snapAwayFromHidden(this.body.hiddenDates,r,1-e,!0),d=m.snapAwayFromHidden(this.body.hiddenDates,a,e-1,!0);h==r&&d==a||(this.props.touch.start=h,this.props.touch.end=d,this.scaleOffset=1-t.scale,r=h,a=d);var l={animation:!1,byUser:!0,event:t};this.setRange(r,a,l),this.startToFront=!1,this.endToFront=!0}},n.prototype._isInsideRange=function(t){var e,i=t.center?t.center.x:t.clientX;e=this.options.rtl?i-c.getAbsoluteLeft(this.body.dom.centerContainer):c.getAbsoluteRight(this.body.dom.centerContainer)-i;var o=this.body.util.toTime(e);return o>=this.start&&o<=this.end},n.prototype._pointerToDate=function(t){var e,i=this.options.direction;if(s(i),"horizontal"==i)return this.body.util.toTime(t.x).valueOf();var o=this.body.domProps.center.height;return e=this.conversion(o),t.y/e.scale+e.offset},n.prototype.getPointer=function(t,e){return this.options.rtl?{x:c.getAbsoluteRight(e)-t.x,y:t.y-c.getAbsoluteTop(e)}:{x:t.x-c.getAbsoluteLeft(e),y:t.y-c.getAbsoluteTop(e)}},n.prototype.zoom=function(t,e,i,o){null==e&&(e=(this.start+this.end)/2);var n=m.getHiddenDurationBetween(this.body.hiddenDates,this.start,this.end),s=m.getHiddenDurationBefore(this.options.moment,this.body.hiddenDates,this,e),r=n-s,a=e-s+(this.start-(e-s))*t,h=e+r+(this.end-(e+r))*t;this.startToFront=!(i>0),this.endToFront=!(-i>0) +;var s=this.options.showMinorLabels&&"none"!==this.options.orientation.axis,r=this.options.showMajorLabels&&"none"!==this.options.orientation.axis;t.minorLabelHeight=s?t.minorCharHeight:0,t.majorLabelHeight=r?t.majorCharHeight:0,t.height=t.minorLabelHeight+t.majorLabelHeight,t.width=e.offsetWidth,t.minorLineHeight=this.body.domProps.root.height-t.majorLabelHeight-("top"==this.options.orientation.axis?this.body.domProps.bottom.height:this.body.domProps.top.height),t.minorLineWidth=1,t.majorLineHeight=t.minorLineHeight+t.majorLabelHeight,t.majorLineWidth=1;var a=e.nextSibling,h=i.nextSibling;return e.parentNode&&e.parentNode.removeChild(e),i.parentNode&&i.parentNode.removeChild(i),e.style.height=this.props.height+"px",this._repaintLabels(),a?o.insertBefore(e,a):o.appendChild(e),h?this.body.dom.backgroundVertical.insertBefore(i,h):this.body.dom.backgroundVertical.appendChild(i),this._isResized()||n},o.prototype._repaintLabels=function(){var t=this.options.orientation.axis,e=r.convert(this.body.range.start,"Number"),i=r.convert(this.body.range.end,"Number"),o=this.body.util.toTime((this.props.minorCharWidth||10)*this.options.maxMinorChars).valueOf(),n=o-d.getHiddenDurationBefore(this.options.moment,this.body.hiddenDates,this.body.range,o);n-=this.body.util.toTime(0).valueOf();var s=new h(new Date(e),new Date(i),n,this.body.hiddenDates,this.options);s.setMoment(this.options.moment),this.options.format&&s.setFormat(this.options.format),this.options.timeAxis&&s.setScale(this.options.timeAxis),this.step=s;var a=this.dom;a.redundant.lines=a.lines,a.redundant.majorTexts=a.majorTexts,a.redundant.minorTexts=a.minorTexts,a.lines=[],a.majorTexts=[],a.minorTexts=[];var l,c,p,f,m,v,g,y,b,_=0,w=void 0,x=0;for(s.start(),l=s.getCurrent(),p=this.body.util.toScreen(l);s.hasNext()&&x<1e3;){switch(x++,f=s.isMajor(),b=s.getClassName(),y=s.getLabelMinor(),l,c=p,s.next(),l=s.getCurrent(),s.isMajor(),p=this.body.util.toScreen(l),v=_,_=p-c,s.scale){case"week":m=!0;break;default:m=_>=.4*v}if(this.options.showMinorLabels&&m){var k=this._repaintMinorText(c,y,t,b);k.style.width=_+"px"}f&&this.options.showMajorLabels?(c>0&&(void 0==w&&(w=c),k=this._repaintMajorText(c,s.getLabelMajor(),t,b)),g=this._repaintMajorLine(c,_,t,b)):m?g=this._repaintMinorLine(c,_,t,b):g&&(g.style.width=parseInt(g.style.width)+_+"px")}if(1e3!==x||u||(console.warn("Something is wrong with the Timeline scale. Limited drawing of grid lines to 1000 lines."),u=!0),this.options.showMajorLabels){var S=this.body.util.toTime(0),D=s.getLabelMajor(S),M=D.length*(this.props.majorCharWidth||10)+10;(void 0==w||Mt.left&&this.shape.topt.top}},{key:"isBoundingBoxOverlappingWith",value:function(t){return this.shape.boundingBox.leftt.left&&this.shape.boundingBox.topt.top}}],[{key:"updateGroupOptions",value:function(t,e,i){if(void 0!==i){var o=t.group;if(void 0!==e&&void 0!==e.group&&o!==e.group)throw new Error("updateGroupOptions: group values in options don't match.");if("number"==typeof o||"string"==typeof o&&""!=o){var n=i.get(o);h.selectiveNotDeepExtend(["font"],t,n),t.color=h.parseColor(t.color)}}}},{key:"parseOptions",value:function(e,i){var o=arguments.length>2&&void 0!==arguments[2]&&arguments[2],n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},s=arguments[4],r=["color","fixed","shadow"];if(h.selectiveNotDeepExtend(r,e,i,o),t.checkMass(i),h.mergeOptions(e,i,"shadow",n),void 0!==i.color&&null!==i.color){var a=h.parseColor(i.color);h.fillIfDefined(e.color,a)}else!0===o&&null===i.color&&(e.color=h.bridgeObject(n.color));void 0!==i.fixed&&null!==i.fixed&&("boolean"==typeof i.fixed?(e.fixed.x=i.fixed,e.fixed.y=i.fixed):(void 0!==i.fixed.x&&"boolean"==typeof i.fixed.x&&(e.fixed.x=i.fixed.x),void 0!==i.fixed.y&&"boolean"==typeof i.fixed.y&&(e.fixed.y=i.fixed.y))),!0===o&&null===i.font&&(e.font=h.bridgeObject(n.font)),t.updateGroupOptions(e,i,s),void 0!==i.scaling&&h.mergeOptions(e.scaling,i.scaling,"label",n.scaling)}},{key:"checkMass",value:function(t,e){if(void 0!==t.mass&&t.mass<=0){var i="";void 0!==e&&(i=" in node id: "+e),console.log("%cNegative or zero mass disallowed"+i+", setting mass to 1.",C),t.mass=1}}}]),t}();e.default=O},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(6),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(2),u=function(){function t(){(0,a.default)(this,t)}return(0,d.default)(t,null,[{key:"choosify",value:function(t,e){var i=["node","edge","label"],o=!0,n=l.topMost(e,"chosen");if("boolean"==typeof n)o=n;else if("object"===(void 0===n?"undefined":(0,s.default)(n))){if(-1===i.indexOf(t))throw new Error("choosify: subOption '"+t+"' should be one of '"+i.join("', '")+"'");var r=l.topMost(e,["chosen",t]);"boolean"!=typeof r&&"function"!=typeof r||(o=r)}return o}},{key:"pointInRect",value:function(t,e,i){if(t.width<=0||t.height<=0)return!1;if(void 0!==i){var o={x:e.x-i.x,y:e.y-i.y};if(0!==i.angle){var n=-i.angle;e={x:Math.cos(n)*o.x-Math.sin(n)*o.y,y:Math.sin(n)*o.x+Math.cos(n)*o.y}}else e=o}var s=t.x+t.width,r=t.y+t.width;return t.lefte.x&&t.tope.y}},{key:"isValidLabel",value:function(t){return"string"==typeof t&&""!==t}}]),t}();e.default=u},function(t,e,i){i(125);for(var o=i(18),n=i(26),s=i(31),r=i(13)("toStringTag"),a="CSSRuleList,CSSStyleDeclaration,CSSValueList,ClientRectList,DOMRectList,DOMStringList,DOMTokenList,DataTransferItemList,FileList,HTMLAllCollection,HTMLCollection,HTMLFormElement,HTMLSelectElement,MediaList,MimeTypeArray,NamedNodeMap,NodeList,PaintRequestList,Plugin,PluginArray,SVGLengthList,SVGNumberList,SVGPathSegList,SVGPointList,SVGStringList,SVGTransformList,SourceBufferList,StyleSheetList,TextTrackCueList,TextTrackList,TouchList".split(","),h=0;hdocument.F=Object<\/script>"),t.close(),h=t.F;o--;)delete h.prototype[s[o]];return h()};t.exports=Object.create||function(t,e){var i;return null!==t?(a.prototype=o(t),i=new a,a.prototype=null,i[r]=t):i=h(),void 0===e?i:n(i,e)}},function(t,e){var i=Math.ceil,o=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?o:i)(t)}},function(t,e,i){var o=i(57)("keys"),n=i(40);t.exports=function(t){return o[t]||(o[t]=n(t))}},function(t,e,i){var o=i(18),n=o["__core-js_shared__"]||(o["__core-js_shared__"]={});t.exports=function(t){return n[t]||(n[t]={})}},function(t,e){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(t,e,i){var o=i(20).f,n=i(22),s=i(13)("toStringTag");t.exports=function(t,e,i){t&&!n(t=i?t:t.prototype,s)&&o(t,s,{configurable:!0,value:e})}},function(t,e,i){var o=i(135)(!0);i(79)(String,"String",function(t){this._t=String(t),this._i=0},function(){var t,e=this._t,i=this._i;return i>=e.length?{value:void 0,done:!0}:(t=o(e,i),this._i+=t.length,{value:t,done:!1})})},function(t,e,i){e.f=i(13)},function(t,e,i){var o=i(18),n=i(7),s=i(52),r=i(61),a=i(20).f;t.exports=function(t){var e=n.Symbol||(n.Symbol=s?{}:o.Symbol||{});"_"==t.charAt(0)||t in e||a(e,t,{value:r.f(t)})}},function(t,e){e.f=Object.getOwnPropertySymbols},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}function n(t,e){var i=p().hours(0).minutes(0).seconds(0).milliseconds(0),o=i.clone().add(-3,"days").valueOf(),n=i.clone().add(3,"days").valueOf();this.millisecondsPerPixelCache=void 0,void 0===e?(this.start=o,this.end=n):(this.start=e.start||o,this.end=e.end||n),this.rolling=!1,this.body=t,this.deltaDifference=0,this.scaleOffset=0,this.startToFront=!1,this.endToFront=!0,this.defaultOptions={rtl:!1,start:null,end:null,moment:p,direction:"horizontal",moveable:!0,zoomable:!0,min:null,max:null,zoomMin:10,zoomMax:31536e10,rollingMode:{follow:!1,offset:.5}},this.options=c.extend({},this.defaultOptions),this.props={touch:{}},this.animationTimer=null,this.body.emitter.on("panstart",this._onDragStart.bind(this)),this.body.emitter.on("panmove",this._onDrag.bind(this)),this.body.emitter.on("panend",this._onDragEnd.bind(this)),this.body.emitter.on("mousewheel",this._onMouseWheel.bind(this)),this.body.emitter.on("touch",this._onTouch.bind(this)),this.body.emitter.on("pinch",this._onPinch.bind(this)),this.body.dom.rollingModeBtn.addEventListener("click",this.startRolling.bind(this)),this.setOptions(e)}function s(t){if("horizontal"!=t&&"vertical"!=t)throw new TypeError('Unknown direction "'+t+'". Choose "horizontal" or "vertical".')}var r=i(8),a=o(r),h=i(19),d=o(h),l=i(6),u=o(l),c=i(2),p=i(9),f=i(16),m=i(36);n.prototype=new f,n.prototype.setOptions=function(t){if(t){var e=["animation","direction","min","max","zoomMin","zoomMax","moveable","zoomable","moment","activate","hiddenDates","zoomKey","rtl","showCurrentTime","rollingMode","horizontalScroll"];c.selectiveExtend(e,this.options,t),t.rollingMode&&t.rollingMode.follow&&this.startRolling(),("start"in t||"end"in t)&&this.setRange(t.start,t.end)}},n.prototype.startRolling=function(){function t(){e.stopRolling(),e.rolling=!0;var i=e.end-e.start,o=c.convert(new Date,"Date").valueOf(),n=o-i*e.options.rollingMode.offset,s=o+i*(1-e.options.rollingMode.offset),r={animation:!1};e.setRange(n,s,r),i=1/e.conversion(e.body.domProps.center.width).scale/10,i<30&&(i=30),i>1e3&&(i=1e3),e.body.dom.rollingModeBtn.style.visibility="hidden",e.currentTimeTimer=setTimeout(t,i)}var e=this;t()},n.prototype.stopRolling=function(){void 0!==this.currentTimeTimer&&(clearTimeout(this.currentTimeTimer),this.rolling=!1,this.body.dom.rollingModeBtn.style.visibility="visible")},n.prototype.setRange=function(t,e,i,o,n){i||(i={}),!0!==i.byUser&&(i.byUser=!1);var s=this,r=void 0!=t?c.convert(t,"Date").valueOf():null,h=void 0!=e?c.convert(e,"Date").valueOf():null;if(this._cancelAnimation(),this.millisecondsPerPixelCache=void 0,i.animation){var l=this.start,p=this.end,f="object"===(0,u.default)(i.animation)&&"duration"in i.animation?i.animation.duration:500,v="object"===(0,u.default)(i.animation)&&"easingFunction"in i.animation?i.animation.easingFunction:"easeInOutQuad",g=c.easingFunctions[v];if(!g)throw new Error("Unknown easing function "+(0,d.default)(v)+". Choose from: "+(0,a.default)(c.easingFunctions).join(", "));var y=(new Date).valueOf(),b=!1;return function t(){if(!s.props.touch.dragging){var e=(new Date).valueOf(),a=e-y,d=g(a/f),u=a>f,c=u||null===r?r:l+(r-l)*d,v=u||null===h?h:p+(h-p)*d;_=s._applyRange(c,v),m.updateHiddenDates(s.options.moment,s.body,s.options.hiddenDates),b=b||_;var w={start:new Date(s.start),end:new Date(s.end),byUser:i.byUser,event:io.cucumber.core.event};if(n&&n(d,_,u),_&&s.body.emitter.emit("rangechange",w),u){if(b&&(s.body.emitter.emit("rangechanged",w),o))return o()}else s.animationTimer=setTimeout(t,20)}}()}var _=this._applyRange(r,h);if(m.updateHiddenDates(this.options.moment,this.body,this.options.hiddenDates),_){var w={start:new Date(this.start),end:new Date(this.end),byUser:i.byUser,event:io.cucumber.core.event};if(this.body.emitter.emit("rangechange",w),clearTimeout(s.timeoutID),s.timeoutID=setTimeout(function(){s.body.emitter.emit("rangechanged",w)},200),o)return o()}},n.prototype.getMillisecondsPerPixel=function(){return void 0===this.millisecondsPerPixelCache&&(this.millisecondsPerPixelCache=(this.end-this.start)/this.body.dom.center.clientWidth),this.millisecondsPerPixelCache},n.prototype._cancelAnimation=function(){this.animationTimer&&(clearTimeout(this.animationTimer),this.animationTimer=null)},n.prototype._applyRange=function(t,e){var i,o=null!=t?c.convert(t,"Date").valueOf():this.start,n=null!=e?c.convert(e,"Date").valueOf():this.end,s=null!=this.options.max?c.convert(this.options.max,"Date").valueOf():null,r=null!=this.options.min?c.convert(this.options.min,"Date").valueOf():null;if(isNaN(o)||null===o)throw new Error('Invalid start "'+t+'"');if(isNaN(n)||null===n)throw new Error('Invalid end "'+e+'"');if(ns&&(n=s)),null!==s&&n>s&&(i=n-s,o-=i,n-=i,null!=r&&o=this.start-.5&&n<=this.end?(o=this.start,n=this.end):(i=a-(n-o),o-=i/2,n+=i/2)}}if(null!==this.options.zoomMax){var h=parseFloat(this.options.zoomMax);h<0&&(h=0),n-o>h&&(this.end-this.start===h&&othis.end?(o=this.start,n=this.end):(i=n-o-h,o+=i/2,n-=i/2))}var d=this.start!=o||this.end!=n;return o>=this.start&&o<=this.end||n>=this.start&&n<=this.end||this.start>=o&&this.start<=n||this.end>=o&&this.end<=n||this.body.emitter.emit("checkRangedItems"),this.start=o,this.end=n,d},n.prototype.getRange=function(){return{start:this.start,end:this.end}},n.prototype.conversion=function(t,e){return n.conversion(this.start,this.end,t,e)},n.conversion=function(t,e,i,o){return void 0===o&&(o=0),0!=i&&e-t!=0?{offset:t,scale:i/(e-t-o)}:{offset:0,scale:1}},n.prototype._onDragStart=function(t){this.deltaDifference=0,this.previousDelta=0,this.options.moveable&&this._isInsideRange(t)&&this.props.touch.allowDragging&&(this.stopRolling(),this.props.touch.start=this.start,this.props.touch.end=this.end,this.props.touch.dragging=!0,this.body.dom.root&&(this.body.dom.root.style.cursor="move"))},n.prototype._onDrag=function(t){if(t&&this.props.touch.dragging&&this.options.moveable&&this.props.touch.allowDragging){var e=this.options.direction;s(e);var i="horizontal"==e?t.deltaX:t.deltaY;i-=this.deltaDifference;var o=this.props.touch.end-this.props.touch.start;o-=m.getHiddenDurationBetween(this.body.hiddenDates,this.start,this.end);var n,r="horizontal"==e?this.body.domProps.center.width:this.body.domProps.center.height;n=this.options.rtl?i/r*o:-i/r*o;var a=this.props.touch.start+n,h=this.props.touch.end+n,d=m.snapAwayFromHidden(this.body.hiddenDates,a,this.previousDelta-i,!0),l=m.snapAwayFromHidden(this.body.hiddenDates,h,this.previousDelta-i,!0);if(d!=a||l!=h)return this.deltaDifference+=i,this.props.touch.start=d,this.props.touch.end=l,void this._onDrag(t);this.previousDelta=i,this._applyRange(a,h);var u=new Date(this.start),c=new Date(this.end);this.body.emitter.emit("rangechange",{start:u,end:c,byUser:!0,event:t}),this.body.emitter.emit("panmove")}},n.prototype._onDragEnd=function(t){this.props.touch.dragging&&this.options.moveable&&this.props.touch.allowDragging&&(this.props.touch.dragging=!1,this.body.dom.root&&(this.body.dom.root.style.cursor="auto"),this.body.emitter.emit("rangechanged",{start:new Date(this.start),end:new Date(this.end),byUser:!0,event:t}))},n.prototype._onMouseWheel=function(t){var e=0;if(t.wheelDelta?e=t.wheelDelta/120:t.detail&&(e=-t.detail/3),!(this.options.zoomKey&&!t[this.options.zoomKey]&&this.options.zoomable||!this.options.zoomable&&this.options.moveable)&&this.options.zoomable&&this.options.moveable&&this._isInsideRange(t)&&e){var i;i=e<0?1-e/5:1/(1+e/5);var o;if(this.rolling)o=this.start+(this.end-this.start)*this.options.rollingMode.offset;else{var n=this.getPointer({x:t.clientX,y:t.clientY},this.body.dom.center);o=this._pointerToDate(n)}this.zoom(i,o,e,t),t.preventDefault()}},n.prototype._onTouch=function(t){this.props.touch.start=this.start,this.props.touch.end=this.end,this.props.touch.allowDragging=!0,this.props.touch.center=null,this.scaleOffset=0,this.deltaDifference=0,c.preventDefault(t)},n.prototype._onPinch=function(t){if(this.options.zoomable&&this.options.moveable){c.preventDefault(t),this.props.touch.allowDragging=!1,this.props.touch.center||(this.props.touch.center=this.getPointer(t.center,this.body.dom.center)),this.stopRolling();var e=1/(t.scale+this.scaleOffset),i=this._pointerToDate(this.props.touch.center),o=m.getHiddenDurationBetween(this.body.hiddenDates,this.start,this.end),n=m.getHiddenDurationBefore(this.options.moment,this.body.hiddenDates,this,i),s=o-n,r=i-n+(this.props.touch.start-(i-n))*e,a=i+s+(this.props.touch.end-(i+s))*e;this.startToFront=1-e<=0,this.endToFront=e-1<=0;var h=m.snapAwayFromHidden(this.body.hiddenDates,r,1-e,!0),d=m.snapAwayFromHidden(this.body.hiddenDates,a,e-1,!0);h==r&&d==a||(this.props.touch.start=h,this.props.touch.end=d,this.scaleOffset=1-t.scale,r=h,a=d);var l={animation:!1,byUser:!0,event:t};this.setRange(r,a,l),this.startToFront=!1,this.endToFront=!0}},n.prototype._isInsideRange=function(t){var e,i=t.center?t.center.x:t.clientX;e=this.options.rtl?i-c.getAbsoluteLeft(this.body.dom.centerContainer):c.getAbsoluteRight(this.body.dom.centerContainer)-i;var o=this.body.util.toTime(e);return o>=this.start&&o<=this.end},n.prototype._pointerToDate=function(t){var e,i=this.options.direction;if(s(i),"horizontal"==i)return this.body.util.toTime(t.x).valueOf();var o=this.body.domProps.center.height;return e=this.conversion(o),t.y/e.scale+e.offset},n.prototype.getPointer=function(t,e){return this.options.rtl?{x:c.getAbsoluteRight(e)-t.x,y:t.y-c.getAbsoluteTop(e)}:{x:t.x-c.getAbsoluteLeft(e),y:t.y-c.getAbsoluteTop(e)}},n.prototype.zoom=function(t,e,i,o){null==e&&(e=(this.start+this.end)/2);var n=m.getHiddenDurationBetween(this.body.hiddenDates,this.start,this.end),s=m.getHiddenDurationBefore(this.options.moment,this.body.hiddenDates,this,e),r=n-s,a=e-s+(this.start-(e-s))*t,h=e+r+(this.end-(e+r))*t;this.startToFront=!(i>0),this.endToFront=!(-i>0) ;var d=m.snapAwayFromHidden(this.body.hiddenDates,a,i,!0),l=m.snapAwayFromHidden(this.body.hiddenDates,h,-i,!0);d==a&&l==h||(a=d,h=l);var u={animation:!1,byUser:!0,event:o};this.setRange(a,h,u),this.startToFront=!1,this.endToFront=!0},n.prototype.move=function(t){var e=this.end-this.start,i=this.start+e*t,o=this.end+e*t;this.start=i,this.end=o},n.prototype.moveTo=function(t){var e=(this.start+this.end)/2,i=e-t,o=this.start-i,n=this.end-i,s={animation:!1,byUser:!0,event:null};this.setRange(o,n,s)},t.exports=n},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}function n(){}var s=i(19),r=o(s),a=i(6),h=o(a),d=i(44),l=i(10),u=i(37),c=i(2),p=i(45),f=i(97),m=i(36),v=i(46);d(n.prototype),n.prototype._create=function(t){function e(t){this.isActive()&&this.emit("mousewheel",t);var e=0,i=0;if("detail"in t&&(i=-1*t.detail),"wheelDelta"in t&&(i=t.wheelDelta),"wheelDeltaY"in t&&(i=t.wheelDeltaY),"wheelDeltaX"in t&&(e=-1*t.wheelDeltaX),"axis"in t&&t.axis===t.HORIZONTAL_AXIS&&(e=-1*i,i=0),"deltaY"in t&&(i=-1*t.deltaY),"deltaX"in t&&(e=t.deltaX),this.options.zoomKey&&!t[this.options.zoomKey])if(t.preventDefault(),this.options.verticalScroll&&Math.abs(i)>=Math.abs(e)){var o=this.props.scrollTop,n=o+i;this.isActive()&&(this._setScrollTop(n),this._redraw(),this.emit("scroll",t))}else if(this.options.horizontalScroll){var s=Math.abs(e)>=Math.abs(i)?e:i,r=s/120*(this.range.end-this.range.start)/20,a=this.range.start+r,h=this.range.end+r,d={animation:!1,byUser:!0,event:t};this.range.setRange(a,h,d)}}function i(t){if(s.options.verticalScroll&&(t.preventDefault(),s.isActive())){var e=-t.target.scrollTop;s._setScrollTop(e),s._redraw(),s.emit("scrollSide",t)}}function o(t){if(t.preventDefault&&t.preventDefault(),!(!t.target.className.indexOf("vis")>-1||a))return t.dataTransfer.dropEffect="move",a=!0,!1}function n(t){t.preventDefault&&t.preventDefault(),t.stopPropagation&&t.stopPropagation();try{var e=JSON.parse(t.dataTransfer.getData("text"));if(!e||!e.content)return}catch(t){return!1}return a=!1,t.center={x:t.clientX,y:t.clientY},"item"!==e.target?s.itemSet._onAddItem(t):s.itemSet._onDropObjectOnItem(t),s.emit("drop",s.getEventProperties(t)),!1}this.dom={},this.dom.container=t,this.dom.root=document.createElement("div"),this.dom.background=document.createElement("div"),this.dom.backgroundVertical=document.createElement("div"),this.dom.backgroundHorizontal=document.createElement("div"),this.dom.centerContainer=document.createElement("div"),this.dom.leftContainer=document.createElement("div"),this.dom.rightContainer=document.createElement("div"),this.dom.center=document.createElement("div"),this.dom.left=document.createElement("div"),this.dom.right=document.createElement("div"),this.dom.top=document.createElement("div"),this.dom.bottom=document.createElement("div"),this.dom.shadowTop=document.createElement("div"),this.dom.shadowBottom=document.createElement("div"),this.dom.shadowTopLeft=document.createElement("div"),this.dom.shadowBottomLeft=document.createElement("div"),this.dom.shadowTopRight=document.createElement("div"),this.dom.shadowBottomRight=document.createElement("div"),this.dom.rollingModeBtn=document.createElement("div"),this.dom.root.className="vis-timeline",this.dom.background.className="vis-panel vis-background",this.dom.backgroundVertical.className="vis-panel vis-background vis-vertical",this.dom.backgroundHorizontal.className="vis-panel vis-background vis-horizontal",this.dom.centerContainer.className="vis-panel vis-center",this.dom.leftContainer.className="vis-panel vis-left",this.dom.rightContainer.className="vis-panel vis-right",this.dom.top.className="vis-panel vis-top",this.dom.bottom.className="vis-panel vis-bottom",this.dom.left.className="vis-content",this.dom.center.className="vis-content",this.dom.right.className="vis-content",this.dom.shadowTop.className="vis-shadow vis-top",this.dom.shadowBottom.className="vis-shadow vis-bottom",this.dom.shadowTopLeft.className="vis-shadow vis-top",this.dom.shadowBottomLeft.className="vis-shadow vis-bottom",this.dom.shadowTopRight.className="vis-shadow vis-top",this.dom.shadowBottomRight.className="vis-shadow vis-bottom",this.dom.rollingModeBtn.className="vis-rolling-mode-btn",this.dom.root.appendChild(this.dom.background),this.dom.root.appendChild(this.dom.backgroundVertical),this.dom.root.appendChild(this.dom.backgroundHorizontal),this.dom.root.appendChild(this.dom.centerContainer),this.dom.root.appendChild(this.dom.leftContainer),this.dom.root.appendChild(this.dom.rightContainer),this.dom.root.appendChild(this.dom.top),this.dom.root.appendChild(this.dom.bottom),this.dom.root.appendChild(this.dom.bottom),this.dom.root.appendChild(this.dom.rollingModeBtn),this.dom.centerContainer.appendChild(this.dom.center),this.dom.leftContainer.appendChild(this.dom.left),this.dom.rightContainer.appendChild(this.dom.right),this.dom.centerContainer.appendChild(this.dom.shadowTop),this.dom.centerContainer.appendChild(this.dom.shadowBottom),this.dom.leftContainer.appendChild(this.dom.shadowTopLeft),this.dom.leftContainer.appendChild(this.dom.shadowBottomLeft),this.dom.rightContainer.appendChild(this.dom.shadowTopRight),this.dom.rightContainer.appendChild(this.dom.shadowBottomRight),this.props={root:{},background:{},centerContainer:{},leftContainer:{},rightContainer:{},center:{},left:{},right:{},top:{},bottom:{},border:{},scrollTop:0,scrollTopMin:0},this.on("rangechange",function(){!0===this.initialDrawDone&&this._redraw()}.bind(this)),this.on("rangechanged",function(){this.initialRangeChangeDone||(this.initialRangeChangeDone=!0)}.bind(this)),this.on("touch",this._onTouch.bind(this)),this.on("panmove",this._onDrag.bind(this));var s=this;this._origRedraw=this._redraw.bind(this),this._redraw=c.throttle(this._origRedraw),this.on("_change",function(t){s.itemSet&&s.itemSet.initialItemSetDrawn&&t&&1==t.queue?s._redraw():s._origRedraw()}),this.hammer=new l(this.dom.root);var r=this.hammer.get("pinch").set({enable:!0});u.disablePreventDefaultVertically(r),this.hammer.get("pan").set({threshold:5,direction:l.DIRECTION_HORIZONTAL}),this.listeners={},["tap","doubletap","press","pinch","pan","panstart","panmove","panend"].forEach(function(t){var e=function(e){s.isActive()&&s.emit(t,e)};s.hammer.on(t,e),s.listeners[t]=e}),u.onTouch(this.hammer,function(t){s.emit("touch",t)}.bind(this)),u.onRelease(this.hammer,function(t){s.emit("release",t)}.bind(this)),this.dom.centerContainer.addEventListener?(this.dom.centerContainer.addEventListener("mousewheel",e.bind(this),!1),this.dom.centerContainer.addEventListener("DOMMouseScroll",e.bind(this),!1)):this.dom.centerContainer.attachEvent("onmousewheel",e.bind(this)),this.dom.left.parentNode.addEventListener("scroll",i.bind(this)),this.dom.right.parentNode.addEventListener("scroll",i.bind(this));var a=!1;if(this.dom.center.addEventListener("dragover",o.bind(this),!1),this.dom.center.addEventListener("drop",n.bind(this),!1),this.customTimes=[],this.touch={},this.redrawCount=0,this.initialDrawDone=!1,this.initialRangeChangeDone=!1,!t)throw new Error("No container provided");t.appendChild(this.dom.root)},n.prototype.setOptions=function(t){if(t){var e=["width","height","minHeight","maxHeight","autoResize","start","end","clickToUse","dataAttributes","hiddenDates","locale","locales","moment","rtl","zoomKey","horizontalScroll","verticalScroll"];if(c.selectiveExtend(e,this.options,t),this.dom.rollingModeBtn.style.visibility="hidden",this.options.rtl&&(this.dom.container.style.direction="rtl",this.dom.backgroundVertical.className="vis-panel vis-background vis-vertical-rtl"),this.options.verticalScroll&&(this.options.rtl?this.dom.rightContainer.className="vis-panel vis-right vis-vertical-scroll":this.dom.leftContainer.className="vis-panel vis-left vis-vertical-scroll"),"object"!==(0,h.default)(this.options.orientation)&&(this.options.orientation={item:void 0,axis:void 0}),"orientation"in t&&("string"==typeof t.orientation?this.options.orientation={item:t.orientation,axis:t.orientation}:"object"===(0,h.default)(t.orientation)&&("item"in t.orientation&&(this.options.orientation.item=t.orientation.item),"axis"in t.orientation&&(this.options.orientation.axis=t.orientation.axis))),"both"===this.options.orientation.axis){if(!this.timeAxis2){var i=this.timeAxis2=new p(this.body);i.setOptions=function(t){var e=t?c.extend({},t):{};e.orientation="top",p.prototype.setOptions.call(i,e)},this.components.push(i)}}else if(this.timeAxis2){var o=this.components.indexOf(this.timeAxis2);-1!==o&&this.components.splice(o,1),this.timeAxis2.destroy(),this.timeAxis2=null}if("function"==typeof t.drawPoints&&(t.drawPoints={onRender:t.drawPoints}),"hiddenDates"in this.options&&m.convertHiddenOptions(this.options.moment,this.body,this.options.hiddenDates),"clickToUse"in t&&(t.clickToUse?this.activator||(this.activator=new f(this.dom.root)):this.activator&&(this.activator.destroy(),delete this.activator)),"showCustomTime"in t)throw new Error("Option `showCustomTime` is deprecated. Create a custom time bar via timeline.addCustomTime(time [, id])");this._initAutoResize()}if(this.components.forEach(function(e){return e.setOptions(t)}),"configure"in t){this.configurator||(this.configurator=this._createConfigurator()),this.configurator.setOptions(t.configure);var n=c.deepExtend({},this.options);this.components.forEach(function(t){c.deepExtend(n,t.options)}),this.configurator.setModuleOptions({global:n})}this._redraw()},n.prototype.isActive=function(){return!this.activator||this.activator.active},n.prototype.destroy=function(){this.setItems(null),this.setGroups(null),this.off(),this._stopAutoResize(),this.dom.root.parentNode&&this.dom.root.parentNode.removeChild(this.dom.root),this.dom=null,this.activator&&(this.activator.destroy(),delete this.activator);for(var t in this.listeners)this.listeners.hasOwnProperty(t)&&delete this.listeners[t];this.listeners=null,this.hammer=null,this.components.forEach(function(t){return t.destroy()}),this.body=null},n.prototype.setCustomTime=function(t,e){var i=this.customTimes.filter(function(t){return e===t.options.id});if(0===i.length)throw new Error("No custom time bar found with id "+(0,r.default)(e));i.length>0&&i[0].setCustomTime(t)},n.prototype.getCustomTime=function(t){var e=this.customTimes.filter(function(e){return e.options.id===t});if(0===e.length)throw new Error("No custom time bar found with id "+(0,r.default)(t));return e[0].getCustomTime()},n.prototype.setCustomTimeTitle=function(t,e){var i=this.customTimes.filter(function(t){return t.options.id===e});if(0===i.length)throw new Error("No custom time bar found with id "+(0,r.default)(e));if(i.length>0)return i[0].setCustomTitle(t)},n.prototype.getEventProperties=function(t){return{event:t}},n.prototype.addCustomTime=function(t,e){var i=void 0!==t?c.convert(t,"Date").valueOf():new Date;if(this.customTimes.some(function(t){return t.options.id===e}))throw new Error("A custom time with id "+(0,r.default)(e)+" already exists");var o=new v(this.body,c.extend({},this.options,{time:i,id:e}));return this.customTimes.push(o),this.components.push(o),this._redraw(),e},n.prototype.removeCustomTime=function(t){var e=this.customTimes.filter(function(e){return e.options.id===t});if(0===e.length)throw new Error("No custom time bar found with id "+(0,r.default)(t));e.forEach(function(t){this.customTimes.splice(this.customTimes.indexOf(t),1),this.components.splice(this.components.indexOf(t),1),t.destroy()}.bind(this))},n.prototype.getVisibleItems=function(){return this.itemSet&&this.itemSet.getVisibleItems()||[]},n.prototype.fit=function(t,e){var i=this.getDataRange();if(null!==i.min||null!==i.max){var o=i.max-i.min,n=new Date(i.min.valueOf()-.01*o),s=new Date(i.max.valueOf()+.01*o),r=!t||void 0===t.animation||t.animation;this.range.setRange(n,s,{animation:r},e)}},n.prototype.getDataRange=function(){throw new Error("Cannot invoke abstract method getDataRange")},n.prototype.setWindow=function(t,e,i,o){"function"==typeof arguments[2]&&(o=arguments[2],i={});var n,s;1==arguments.length?(s=arguments[0],n=void 0===s.animation||s.animation,this.range.setRange(s.start,s.end,{animation:n})):2==arguments.length&&"function"==typeof arguments[1]?(s=arguments[0],o=arguments[1],n=void 0===s.animation||s.animation,this.range.setRange(s.start,s.end,{animation:n},o)):(n=!i||void 0===i.animation||i.animation,this.range.setRange(t,e,{animation:n},o))},n.prototype.moveTo=function(t,e,i){"function"==typeof arguments[1]&&(i=arguments[1],e={});var o=this.range.end-this.range.start,n=c.convert(t,"Date").valueOf(),s=n-o/2,r=n+o/2,a=!e||void 0===e.animation||e.animation;this.range.setRange(s,r,{animation:a},i)},n.prototype.getWindow=function(){var t=this.range.getRange();return{start:new Date(t.start),end:new Date(t.end)}},n.prototype.zoomIn=function(t,e,i){if(!(!t||t<0||t>1)){"function"==typeof arguments[1]&&(i=arguments[1],e={});var o=this.getWindow(),n=o.start.valueOf(),s=o.end.valueOf(),r=s-n,a=r/(1+t),h=(r-a)/2,d=n+h,l=s-h;this.setWindow(d,l,e,i)}},n.prototype.zoomOut=function(t,e,i){if(!(!t||t<0||t>1)){"function"==typeof arguments[1]&&(i=arguments[1],e={});var o=this.getWindow(),n=o.start.valueOf(),s=o.end.valueOf(),r=s-n,a=n-r*t/2,h=s+r*t/2;this.setWindow(a,h,e,i)}},n.prototype.redraw=function(){this._redraw()},n.prototype._redraw=function(){this.redrawCount++;var t=!1,e=this.options,i=this.props,o=this.dom;if(o&&o.container&&0!=o.root.offsetWidth){m.updateHiddenDates(this.options.moment,this.body,this.options.hiddenDates),"top"==e.orientation?(c.addClassName(o.root,"vis-top"),c.removeClassName(o.root,"vis-bottom")):(c.removeClassName(o.root,"vis-top"),c.addClassName(o.root,"vis-bottom")),o.root.style.maxHeight=c.option.asSize(e.maxHeight,""),o.root.style.minHeight=c.option.asSize(e.minHeight,""),o.root.style.width=c.option.asSize(e.width,""),i.border.left=(o.centerContainer.offsetWidth-o.centerContainer.clientWidth)/2,i.border.right=i.border.left,i.border.top=(o.centerContainer.offsetHeight-o.centerContainer.clientHeight)/2,i.border.bottom=i.border.top,i.borderRootHeight=o.root.offsetHeight-o.root.clientHeight,i.borderRootWidth=o.root.offsetWidth-o.root.clientWidth,0===o.centerContainer.clientHeight&&(i.border.left=i.border.top,i.border.right=i.border.left),0===o.root.clientHeight&&(i.borderRootWidth=i.borderRootHeight),i.center.height=o.center.offsetHeight,i.left.height=o.left.offsetHeight,i.right.height=o.right.offsetHeight,i.top.height=o.top.clientHeight||-i.border.top,i.bottom.height=o.bottom.clientHeight||-i.border.bottom;var n=Math.max(i.left.height,i.center.height,i.right.height),s=i.top.height+n+i.bottom.height+i.borderRootHeight+i.border.top+i.border.bottom;o.root.style.height=c.option.asSize(e.height,s+"px"),i.root.height=o.root.offsetHeight,i.background.height=i.root.height-i.borderRootHeight;var r=i.root.height-i.top.height-i.bottom.height-i.borderRootHeight;i.centerContainer.height=r,i.leftContainer.height=r,i.rightContainer.height=i.leftContainer.height,i.root.width=o.root.offsetWidth,i.background.width=i.root.width-i.borderRootWidth,this.initialDrawDone||(i.scrollbarWidth=c.getScrollBarWidth()),e.verticalScroll?e.rtl?(i.left.width=o.leftContainer.clientWidth||-i.border.left,i.right.width=o.rightContainer.clientWidth+i.scrollbarWidth||-i.border.right):(i.left.width=o.leftContainer.clientWidth+i.scrollbarWidth||-i.border.left,i.right.width=o.rightContainer.clientWidth||-i.border.right):(i.left.width=o.leftContainer.clientWidth||-i.border.left,i.right.width=o.rightContainer.clientWidth||-i.border.right),this._setDOM();var a=this._updateScrollTop();"top"!=e.orientation.item&&(a+=Math.max(i.centerContainer.height-i.center.height-i.border.top-i.border.bottom,0)),o.center.style.top=a+"px";var h=0==i.scrollTop?"hidden":"",d=i.scrollTop==i.scrollTopMin?"hidden":"";o.shadowTop.style.visibility=h,o.shadowBottom.style.visibility=d,o.shadowTopLeft.style.visibility=h,o.shadowBottomLeft.style.visibility=d,o.shadowTopRight.style.visibility=h,o.shadowBottomRight.style.visibility=d,e.verticalScroll&&(o.rightContainer.className="vis-panel vis-right vis-vertical-scroll",o.leftContainer.className="vis-panel vis-left vis-vertical-scroll",o.shadowTopRight.style.visibility="hidden",o.shadowBottomRight.style.visibility="hidden",o.shadowTopLeft.style.visibility="hidden",o.shadowBottomLeft.style.visibility="hidden",o.left.style.top="0px",o.right.style.top="0px"),(!e.verticalScroll||i.center.heighti.centerContainer.height;this.hammer.get("pan").set({direction:u?l.DIRECTION_ALL:l.DIRECTION_HORIZONTAL}),this.components.forEach(function(e){t=e.redraw()||t});if(t){if(this.redrawCount<5)return void this.body.emitter.emit("_change");console.log("WARNING: infinite loop in redraw?")}else this.redrawCount=0;this.body.emitter.emit("changed")}},n.prototype._setDOM=function(){var t=this.props,e=this.dom;t.leftContainer.width=t.left.width,t.rightContainer.width=t.right.width;var i=t.root.width-t.left.width-t.right.width-t.borderRootWidth;t.center.width=i,t.centerContainer.width=i,t.top.width=i,t.bottom.width=i,e.background.style.height=t.background.height+"px",e.backgroundVertical.style.height=t.background.height+"px",e.backgroundHorizontal.style.height=t.centerContainer.height+"px",e.centerContainer.style.height=t.centerContainer.height+"px",e.leftContainer.style.height=t.leftContainer.height+"px",e.rightContainer.style.height=t.rightContainer.height+"px",e.background.style.width=t.background.width+"px",e.backgroundVertical.style.width=t.centerContainer.width+"px",e.backgroundHorizontal.style.width=t.background.width+"px",e.centerContainer.style.width=t.center.width+"px",e.top.style.width=t.top.width+"px",e.bottom.style.width=t.bottom.width+"px",e.background.style.left="0",e.background.style.top="0",e.backgroundVertical.style.left=t.left.width+t.border.left+"px",e.backgroundVertical.style.top="0",e.backgroundHorizontal.style.left="0",e.backgroundHorizontal.style.top=t.top.height+"px",e.centerContainer.style.left=t.left.width+"px",e.centerContainer.style.top=t.top.height+"px",e.leftContainer.style.left="0",e.leftContainer.style.top=t.top.height+"px",e.rightContainer.style.left=t.left.width+t.center.width+"px",e.rightContainer.style.top=t.top.height+"px",e.top.style.left=t.left.width+"px",e.top.style.top="0",e.bottom.style.left=t.left.width+"px",e.bottom.style.top=t.top.height+t.centerContainer.height+"px",e.center.style.left="0",e.left.style.left="0",e.right.style.left="0"},n.prototype.repaint=function(){throw new Error("Function repaint is deprecated. Use redraw instead.")},n.prototype.setCurrentTime=function(t){if(!this.currentTime)throw new Error("Option showCurrentTime must be true");this.currentTime.setCurrentTime(t)},n.prototype.getCurrentTime=function(){if(!this.currentTime)throw new Error("Option showCurrentTime must be true");return this.currentTime.getCurrentTime()},n.prototype._toTime=function(t){return m.toTime(this,t,this.props.center.width)},n.prototype._toGlobalTime=function(t){return m.toTime(this,t,this.props.root.width)},n.prototype._toScreen=function(t){return m.toScreen(this,t,this.props.center.width)},n.prototype._toGlobalScreen=function(t){return m.toScreen(this,t,this.props.root.width)},n.prototype._initAutoResize=function(){1==this.options.autoResize?this._startAutoResize():this._stopAutoResize()},n.prototype._startAutoResize=function(){var t=this;this._stopAutoResize(),this._onResize=function(){if(1!=t.options.autoResize)return void t._stopAutoResize();t.dom.root&&(t.dom.root.offsetWidth==t.props.lastWidth&&t.dom.root.offsetHeight==t.props.lastHeight||(t.props.lastWidth=t.dom.root.offsetWidth,t.props.lastHeight=t.dom.root.offsetHeight,t.props.scrollbarWidth=c.getScrollBarWidth(),t.body.emitter.emit("_change")))},c.addEventListener(window,"resize",this._onResize),t.dom.root&&(t.props.lastWidth=t.dom.root.offsetWidth,t.props.lastHeight=t.dom.root.offsetHeight),this.watchTimer=setInterval(this._onResize,1e3)},n.prototype._stopAutoResize=function(){this.watchTimer&&(clearInterval(this.watchTimer),this.watchTimer=void 0),this._onResize&&(c.removeEventListener(window,"resize",this._onResize),this._onResize=null)},n.prototype._onTouch=function(t){this.touch.allowDragging=!0,this.touch.initialScrollTop=this.props.scrollTop},n.prototype._onPinch=function(t){this.touch.allowDragging=!1},n.prototype._onDrag=function(t){if(t&&this.touch.allowDragging){var e=t.deltaY,i=this._getScrollTop(),o=this._setScrollTop(this.touch.initialScrollTop+e);this.options.verticalScroll&&(this.dom.left.parentNode.scrollTop=-this.props.scrollTop,this.dom.right.parentNode.scrollTop=-this.props.scrollTop),o!=i&&this.emit("verticalDrag")}},n.prototype._setScrollTop=function(t){return this.props.scrollTop=t,this._updateScrollTop(),this.props.scrollTop},n.prototype._updateScrollTop=function(){var t=Math.min(this.props.centerContainer.height-this.props.center.height,0);return t!=this.props.scrollTopMin&&("top"!=this.options.orientation.item&&(this.props.scrollTop+=t-this.props.scrollTopMin),this.props.scrollTopMin=t),this.props.scrollTop>0&&(this.props.scrollTop=0),this.props.scrollTop0&&this.current.milliseconds()0&&this.current.seconds()0&&this.current.minutes()0&&this.current.hours()0?t.step:1,this.autoScale=!1)},o.prototype.setAutoScale=function(t){this.autoScale=t},o.prototype.setMinimumStep=function(t){if(void 0!=t){31104e9>t&&(this.scale="year",this.step=1e3),15552e9>t&&(this.scale="year",this.step=500),31104e8>t&&(this.scale="year",this.step=100),15552e8>t&&(this.scale="year",this.step=50),31104e7>t&&(this.scale="year",this.step=10),15552e7>t&&(this.scale="year",this.step=5),31104e6>t&&(this.scale="year",this.step=1),7776e6>t&&(this.scale="month",this.step=3),2592e6>t&&(this.scale="month",this.step=1),432e6>t&&(this.scale="day",this.step=5),1728e5>t&&(this.scale="day",this.step=2),864e5>t&&(this.scale="day",this.step=1),432e5>t&&(this.scale="weekday",this.step=1),144e5>t&&(this.scale="hour",this.step=4),36e5>t&&(this.scale="hour",this.step=1),9e5>t&&(this.scale="minute",this.step=15),6e5>t&&(this.scale="minute",this.step=10),3e5>t&&(this.scale="minute",this.step=5),6e4>t&&(this.scale="minute",this.step=1),15e3>t&&(this.scale="second",this.step=15),1e4>t&&(this.scale="second",this.step=10),5e3>t&&(this.scale="second",this.step=5),1e3>t&&(this.scale="second",this.step=1),200>t&&(this.scale="millisecond",this.step=200),100>t&&(this.scale="millisecond",this.step=100),50>t&&(this.scale="millisecond",this.step=50),10>t&&(this.scale="millisecond",this.step=10),5>t&&(this.scale="millisecond",this.step=5),1>t&&(this.scale="millisecond",this.step=1)}},o.snap=function(t,e,i){var o=n(t);if("year"==e){var s=o.year()+Math.round(o.month()/12);o.year(Math.round(s/i)*i),o.month(0),o.date(0),o.hours(0),o.minutes(0),o.seconds(0),o.milliseconds(0)}else if("month"==e)o.date()>15?(o.date(1),o.add(1,"month")):o.date(1),o.hours(0),o.minutes(0),o.seconds(0),o.milliseconds(0);else if("week"==e)o.weekday()>2?(o.weekday(0),o.add(1,"week")):o.weekday(0),o.hours(0),o.minutes(0),o.seconds(0),o.milliseconds(0);else if("day"==e){switch(i){case 5:case 2:o.hours(24*Math.round(o.hours()/24));break;default:o.hours(12*Math.round(o.hours()/12))}o.minutes(0),o.seconds(0),o.milliseconds(0)}else if("weekday"==e){switch(i){case 5:case 2:o.hours(12*Math.round(o.hours()/12));break;default:o.hours(6*Math.round(o.hours()/6))}o.minutes(0),o.seconds(0),o.milliseconds(0)}else if("hour"==e){switch(i){case 4:o.minutes(60*Math.round(o.minutes()/60));break;default:o.minutes(30*Math.round(o.minutes()/30))}o.seconds(0),o.milliseconds(0)}else if("minute"==e){switch(i){case 15:case 10:o.minutes(5*Math.round(o.minutes()/5)),o.seconds(0);break;case 5:o.seconds(60*Math.round(o.seconds()/60));break;default:o.seconds(30*Math.round(o.seconds()/30))}o.milliseconds(0)}else if("second"==e)switch(i){case 15:case 10:o.seconds(5*Math.round(o.seconds()/5)),o.milliseconds(0);break;case 5:o.milliseconds(1e3*Math.round(o.milliseconds()/1e3));break;default:o.milliseconds(500*Math.round(o.milliseconds()/500))}else if("millisecond"==e){var r=i>5?i/2:1;o.milliseconds(Math.round(o.milliseconds()/r)*r)}return o},o.prototype.isMajor=function(){if(1==this.switchedYear)switch(this.scale){case"year":case"month":case"week":case"weekday":case"day":case"hour":case"minute":case"second":case"millisecond":return!0;default:return!1}else if(1==this.switchedMonth)switch(this.scale){case"week":case"weekday":case"day":case"hour":case"minute":case"second":case"millisecond":return!0;default:return!1}else if(1==this.switchedDay)switch(this.scale){case"millisecond":case"second":case"minute":case"hour":return!0;default:return!1}var t=this.moment(this.current);switch(this.scale){case"millisecond":return 0==t.milliseconds();case"second":return 0==t.seconds();case"minute":return 0==t.hours()&&0==t.minutes();case"hour":return 0==t.hours();case"weekday":case"day":case"week":return 1==t.date();case"month":return 0==t.month();case"year":default:return!1}},o.prototype.getLabelMinor=function(t){if(void 0==t&&(t=this.current),t instanceof Date&&(t=this.moment(t)),"function"==typeof this.format.minorLabels)return this.format.minorLabels(t,this.scale,this.step);var e=this.format.minorLabels[this.scale];switch(this.scale){case"week":if(this.isMajor()&&0!==t.weekday())return"";default:return e&&e.length>0?this.moment(t).format(e):""}},o.prototype.getLabelMajor=function(t){if(void 0==t&&(t=this.current),t instanceof Date&&(t=this.moment(t)),"function"==typeof this.format.majorLabels)return this.format.majorLabels(t,this.scale,this.step);var e=this.format.majorLabels[this.scale];return e&&e.length>0?this.moment(t).format(e):""},o.prototype.getClassName=function(){function t(t){return t/a%2==0?" vis-even":" vis-odd"}function e(t){return t.isSame(new Date,"day")?" vis-today":t.isSame(n().add(1,"day"),"day")?" vis-tomorrow":t.isSame(n().add(-1,"day"),"day")?" vis-yesterday":""}function i(t){return t.isSame(new Date,"week")?" vis-current-week":""}function o(t){return t.isSame(new Date,"month")?" vis-current-month":""}var n=this.moment,s=this.moment(this.current),r=s.locale?s.locale("en"):s.lang("en"),a=this.step,h=[];switch(this.scale){case"millisecond":h.push(e(r)),h.push(t(r.milliseconds()));break;case"second":h.push(e(r)),h.push(t(r.seconds()));break;case"minute":h.push(e(r)),h.push(t(r.minutes()));break;case"hour":h.push("vis-h"+r.hours()+(4==this.step?"-h"+(r.hours()+4):"")),h.push(e(r)),h.push(t(r.hours()));break;case"weekday":h.push("vis-"+r.format("dddd").toLowerCase()),h.push(e(r)),h.push(i(r)),h.push(t(r.date()));break;case"day":h.push("vis-day"+r.date()),h.push("vis-"+r.format("MMMM").toLowerCase()),h.push(e(r)),h.push(o(r)),h.push(this.step<=2?e(r):""),h.push(this.step<=2?"vis-"+r.format("dddd").toLowerCase():""), h.push(t(r.date()-1));break;case"week":h.push("vis-week"+r.format("w")),h.push(i(r)),h.push(t(r.week()));break;case"month":h.push("vis-"+r.format("MMMM").toLowerCase()),h.push(o(r)),h.push(t(r.month()));break;case"year":h.push("vis-year"+r.year()),h.push(function(t){return t.isSame(new Date,"year")?" vis-current-year":""}(r)),h.push(t(r.year()))}return h.filter(String).join(" ")},t.exports=o},function(t,e,i){function o(t,e){this.body=t,this.defaultOptions={rtl:!1,showCurrentTime:!0,moment:r,locales:a,locale:"en"},this.options=n.extend({},this.defaultOptions),this.offset=0,this._create(),this.setOptions(e)}var n=i(2),s=i(16),r=i(9),a=i(98);o.prototype=new s,o.prototype._create=function(){var t=document.createElement("div");t.className="vis-current-time",t.style.position="absolute",t.style.top="0px",t.style.height="100%",this.bar=t},o.prototype.destroy=function(){this.options.showCurrentTime=!1,this.redraw(),this.body=null},o.prototype.setOptions=function(t){t&&n.selectiveExtend(["rtl","showCurrentTime","moment","locale","locales"],this.options,t)},o.prototype.redraw=function(){if(this.options.showCurrentTime){var t=this.body.dom.backgroundVertical;this.bar.parentNode!=t&&(this.bar.parentNode&&this.bar.parentNode.removeChild(this.bar),t.appendChild(this.bar),this.start());var e=this.options.moment((new Date).valueOf()+this.offset),i=this.body.util.toScreen(e),o=this.options.locales[this.options.locale];o||(this.warned||(console.log("WARNING: options.locales['"+this.options.locale+"'] not found. See http://visjs.org/docs/timeline/#Localization"),this.warned=!0),o=this.options.locales.en);var n=o.current+" "+o.time+": "+e.format("dddd, MMMM Do YYYY, H:mm:ss");n=n.charAt(0).toUpperCase()+n.substring(1),this.options.rtl?this.bar.style.right=i+"px":this.bar.style.left=i+"px",this.bar.title=n}else this.bar.parentNode&&this.bar.parentNode.removeChild(this.bar),this.stop();return!1},o.prototype.start=function(){function t(){e.stop();var i=e.body.range.conversion(e.body.domProps.center.width).scale,o=1/i/10;o<30&&(o=30),o>1e3&&(o=1e3),e.redraw(),e.body.emitter.emit("currentTimeTick"),e.currentTimeTimer=setTimeout(t,o)}var e=this;t()},o.prototype.stop=function(){void 0!==this.currentTimeTimer&&(clearTimeout(this.currentTimeTimer),delete this.currentTimeTimer)},o.prototype.setCurrentTime=function(t){var e=n.convert(t,"Date").valueOf(),i=(new Date).valueOf();this.offset=e-i,this.redraw()},o.prototype.getCurrentTime=function(){return new Date((new Date).valueOf()+this.offset)},t.exports=o},function(t,e,i){function o(t,e,i){if(this.groupId=t,this.subgroups={},this.subgroupStack={},this.subgroupStackAll=!1,this.doInnerStack=!1,this.subgroupIndex=0,this.subgroupOrderer=e&&e.subgroupOrder,this.itemSet=i,this.isVisible=null,this.stackDirty=!0,e&&e.nestedGroups&&(this.nestedGroups=e.nestedGroups,0==e.showNested?this.showNested=!1:this.showNested=!0),e&&e.subgroupStack)if("boolean"==typeof e.subgroupStack)this.doInnerStack=e.subgroupStack,this.subgroupStackAll=e.subgroupStack;else for(var o in e.subgroupStack)this.subgroupStack[o]=e.subgroupStack[o],this.doInnerStack=this.doInnerStack||e.subgroupStack[o];this.nestedInGroup=null,this.dom={},this.props={label:{width:0,height:0}},this.className=null,this.items={},this.visibleItems=[],this.itemsInRange=[],this.orderedItems={byStart:[],byEnd:[]},this.checkRangedItems=!1;var n=this;this.itemSet.body.emitter.on("checkRangedItems",function(){n.checkRangedItems=!0}),this._create(),this.setData(e)}var n=i(8),s=function(t){return t&&t.__esModule?t:{default:t}}(n),r=i(2),a=i(100);o.prototype._create=function(){var t=document.createElement("div");this.itemSet.options.groupEditable.order?t.className="vis-label draggable":t.className="vis-label",this.dom.label=t;var e=document.createElement("div");e.className="vis-inner",t.appendChild(e),this.dom.inner=e;var i=document.createElement("div");i.className="vis-group",i["timeline-group"]=this,this.dom.foreground=i,this.dom.background=document.createElement("div"),this.dom.background.className="vis-group",this.dom.axis=document.createElement("div"),this.dom.axis.className="vis-group",this.dom.marker=document.createElement("div"),this.dom.marker.style.visibility="hidden",this.dom.marker.style.position="absolute",this.dom.marker.innerHTML="",this.dom.background.appendChild(this.dom.marker)},o.prototype.setData=function(t){var e,i;if(this.itemSet.options&&this.itemSet.options.groupTemplate?(i=this.itemSet.options.groupTemplate.bind(this),e=i(t,this.dom.inner)):e=t&&t.content,e instanceof Element){for(this.dom.inner.appendChild(e);this.dom.inner.firstChild;)this.dom.inner.removeChild(this.dom.inner.firstChild);this.dom.inner.appendChild(e)}else e instanceof Object?i(t,this.dom.inner):this.dom.inner.innerHTML=void 0!==e&&null!==e?e:this.groupId||"";if(this.dom.label.title=t&&t.title||"",this.dom.inner.firstChild?r.removeClassName(this.dom.inner,"vis-hidden"):r.addClassName(this.dom.inner,"vis-hidden"),t&&t.nestedGroups){this.nestedGroups&&this.nestedGroups==t.nestedGroups||(this.nestedGroups=t.nestedGroups),void 0===t.showNested&&void 0!==this.showNested||(0==t.showNested?this.showNested=!1:this.showNested=!0),r.addClassName(this.dom.label,"vis-nesting-group");var o=this.itemSet.options.rtl?"collapsed-rtl":"collapsed";this.showNested?(r.removeClassName(this.dom.label,o),r.addClassName(this.dom.label,"expanded")):(r.removeClassName(this.dom.label,"expanded"),r.addClassName(this.dom.label,o))}else this.nestedGroups&&(this.nestedGroups=null,o=this.itemSet.options.rtl?"collapsed-rtl":"collapsed",r.removeClassName(this.dom.label,o),r.removeClassName(this.dom.label,"expanded"),r.removeClassName(this.dom.label,"vis-nesting-group"));t&&t.nestedInGroup&&(r.addClassName(this.dom.label,"vis-nested-group"),this.itemSet.options&&this.itemSet.options.rtl?this.dom.inner.style.paddingRight="30px":this.dom.inner.style.paddingLeft="30px");var n=t&&t.className||null;n!=this.className&&(this.className&&(r.removeClassName(this.dom.label,this.className),r.removeClassName(this.dom.foreground,this.className),r.removeClassName(this.dom.background,this.className),r.removeClassName(this.dom.axis,this.className)),r.addClassName(this.dom.label,n),r.addClassName(this.dom.foreground,n),r.addClassName(this.dom.background,n),r.addClassName(this.dom.axis,n),this.className=n),this.style&&(r.removeCssText(this.dom.label,this.style),this.style=null),t&&t.style&&(r.addCssText(this.dom.label,t.style),this.style=t.style)},o.prototype.getLabelWidth=function(){return this.props.label.width},o.prototype._didMarkerHeightChange=function(){var t=this.dom.marker.clientHeight;if(t!=this.lastMarkerHeight){this.lastMarkerHeight=t;var e={},i=0;r.forEach(this.items,function(t,o){if(t.dirty=!0,t.displayed){e[o]=t.redraw(!0),i=e[o].length}});if(i>0)for(var o=0;o0)for(var u=0;u0){var e=this;this.resetSubgroups(),r.forEach(this.visibleItems,function(i){void 0!==i.data.subgroup&&(e.subgroups[i.data.subgroup].height=Math.max(e.subgroups[i.data.subgroup].height,i.height+t.item.vertical),e.subgroups[i.data.subgroup].visible=!0)})}},o.prototype._isGroupVisible=function(t,e){return this.top<=t.body.domProps.centerContainer.height-t.body.domProps.scrollTop+e.axis&&this.top+this.height+e.axis>=-t.body.domProps.scrollTop},o.prototype._calculateHeight=function(t){var e,i=this.visibleItems;if(i.length>0){var o=i[0].top,n=i[0].top+i[0].height;if(r.forEach(i,function(t){o=Math.min(o,t.top),n=Math.max(n,t.top+t.height)}),o>t.axis){var s=o-t.axis;n-=s,r.forEach(i,function(t){t.top-=s})}e=n+t.item.vertical/2}else e=0;return e=Math.max(e,this.props.label.height)},o.prototype.show=function(){this.dom.label.parentNode||this.itemSet.dom.labelSet.appendChild(this.dom.label),this.dom.foreground.parentNode||this.itemSet.dom.foreground.appendChild(this.dom.foreground),this.dom.background.parentNode||this.itemSet.dom.background.appendChild(this.dom.background),this.dom.axis.parentNode||this.itemSet.dom.axis.appendChild(this.dom.axis)},o.prototype.hide=function(){var t=this.dom.label;t.parentNode&&t.parentNode.removeChild(t);var e=this.dom.foreground;e.parentNode&&e.parentNode.removeChild(e);var i=this.dom.background;i.parentNode&&i.parentNode.removeChild(i);var o=this.dom.axis;o.parentNode&&o.parentNode.removeChild(o)},o.prototype.add=function(t){if(this.items[t.id]=t,t.setParent(this),this.stackDirty=!0,void 0!==t.data.subgroup&&(this._addToSubgroup(t),this.orderSubgroups()),-1==this.visibleItems.indexOf(t)){var e=this.itemSet.body.range;this._checkIfVisible(t,this.visibleItems,e)}},o.prototype._addToSubgroup=function(t,e){e=e||t.data.subgroup,void 0!=e&&void 0===this.subgroups[e]&&(this.subgroups[e]={height:0,top:0,start:t.data.start,end:t.data.end||t.data.start,visible:!1,index:this.subgroupIndex,items:[],stack:this.subgroupStackAll||this.subgroupStack[e]||!1},this.subgroupIndex++),new Date(t.data.start)new Date(this.subgroups[e].end)&&(this.subgroups[e].end=i),this.subgroups[e].items.push(t)},o.prototype._updateSubgroupsSizes=function(){var t=this;if(t.subgroups)for(var e in t.subgroups){var i=t.subgroups[e].items[0].data.end||t.subgroups[e].items[0].data.start,o=t.subgroups[e].items[0].data.start,n=i-1;t.subgroups[e].items.forEach(function(t){new Date(t.data.start)new Date(n)&&(n=e)}),t.subgroups[e].start=o,t.subgroups[e].end=new Date(n-1)}},o.prototype.orderSubgroups=function(){if(void 0!==this.subgroupOrderer){var t,e=[];if("string"==typeof this.subgroupOrderer){for(t in this.subgroups)e.push({subgroup:t,sortField:this.subgroups[t].items[0].data[this.subgroupOrderer]});e.sort(function(t,e){return t.sortField-e.sortField})}else if("function"==typeof this.subgroupOrderer){for(t in this.subgroups)e.push(this.subgroups[t].items[0].data);e.sort(this.subgroupOrderer)}if(e.length>0)for(var i=0;i=0&&(i.items.splice(o,1),i.items.length?this._updateSubgroupsSizes():delete this.subgroups[e])}}},o.prototype.removeFromDataSet=function(t){this.itemSet.removeItem(t.id)},o.prototype.order=function(){for(var t=r.toArray(this.items),e=[],i=[],o=0;o0)for(var l=0;lh}),1==this.checkRangedItems)for(this.checkRangedItems=!1,l=0;lh})}var p={},f=0;for(l=0;l0)for(var v=0;v=0&&(r=e[s],!n(r));s--)void 0===o[r.id]&&(o[r.id]=!0,i.push(r));for(s=t+1;st.start},o.prototype._createDomElement=function(){this.dom||(this.dom={},this.dom.box=document.createElement("div"),this.dom.frame=document.createElement("div"),this.dom.frame.className="vis-item-overflow",this.dom.box.appendChild(this.dom.frame),this.dom.visibleFrame=document.createElement("div"),this.dom.visibleFrame.className="vis-item-visible-frame",this.dom.box.appendChild(this.dom.visibleFrame),this.dom.content=document.createElement("div"),this.dom.content.className="vis-item-content",this.dom.frame.appendChild(this.dom.content),this.dom.box["timeline-item"]=this,this.dirty=!0)},o.prototype._appendDomElement=function(){if(!this.parent)throw new Error("Cannot redraw item: no parent attached");if(!this.dom.box.parentNode){var t=this.parent.dom.foreground;if(!t)throw new Error("Cannot redraw item: parent has no foreground container element");t.appendChild(this.dom.box)}this.displayed=!0},o.prototype._updateDirtyDomComponents=function(){if(this.dirty){this._updateContents(this.dom.content),this._updateDataAttributes(this.dom.box),this._updateStyle(this.dom.box);var t=this.editable.updateTime||this.editable.updateGroup,e=(this.data.className?" "+this.data.className:"")+(this.selected?" vis-selected":"")+(t?" vis-editable":" vis-readonly");this.dom.box.className=this.baseClassName+e,this.dom.content.style.maxWidth="none"}},o.prototype._getDomComponentsSizes=function(){return this.overflow="hidden"!==window.getComputedStyle(this.dom.frame).overflow,{content:{width:this.dom.content.offsetWidth},box:{height:this.dom.box.offsetHeight}}},o.prototype._updateDomComponentsSizes=function(t){this.props.content.width=t.content.width,this.height=t.box.height,this.dom.content.style.maxWidth="",this.dirty=!1},o.prototype._repaintDomAdditionals=function(){this._repaintOnItemUpdateTimeTooltip(this.dom.box),this._repaintDeleteButton(this.dom.box),this._repaintDragCenter(),this._repaintDragLeft(),this._repaintDragRight()},o.prototype.redraw=function(t){var e,i=[this._createDomElement.bind(this),this._appendDomElement.bind(this),this._updateDirtyDomComponents.bind(this),function(){this.dirty&&(e=this._getDomComponentsSizes.bind(this)())}.bind(this),function(){this.dirty&&this._updateDomComponentsSizes.bind(this)(e)}.bind(this),this._repaintDomAdditionals.bind(this)];if(t)return i;var o;return i.forEach(function(t){o=t()}),o},o.prototype.show=function(){this.displayed||this.redraw()},o.prototype.hide=function(){if(this.displayed){var t=this.dom.box;t.parentNode&&t.parentNode.removeChild(t),this.displayed=!1}},o.prototype.repositionX=function(t){var e,i,o=this.parent.width,n=this.conversion.toScreen(this.data.start),s=this.conversion.toScreen(this.data.end),r=void 0===this.data.align?this.options.align:this.data.align;!1===this.data.limitSize||void 0!==t&&!0!==t||(n<-o&&(n=-o),s>2*o&&(s=2*o));var a=Math.max(s-n+.5,1);switch(this.overflow?(this.options.rtl?this.right=n:this.left=n,this.width=a+this.props.content.width,i=this.props.content.width):(this.options.rtl?this.right=n:this.left=n,this.width=a,i=Math.min(s-n,this.props.content.width)),this.options.rtl?this.dom.box.style.right=this.right+"px":this.dom.box.style.left=this.left+"px",this.dom.box.style.width=a+"px",r){case"left":this.options.rtl?this.dom.content.style.right="0":this.dom.content.style.left="0";break;case"right":this.options.rtl?this.dom.content.style.right=Math.max(a-i,0)+"px":this.dom.content.style.left=Math.max(a-i,0)+"px";break;case"center":this.options.rtl?this.dom.content.style.right=Math.max((a-i)/2,0)+"px":this.dom.content.style.left=Math.max((a-i)/2,0)+"px";break;default:e=this.overflow?s>0?Math.max(-n,0):-i:n<0?-n:0,this.options.rtl?this.dom.content.style.right=e+"px":(this.dom.content.style.left=e+"px",this.dom.content.style.width="calc(100% - "+e+"px)")}},o.prototype.repositionY=function(){var t=this.options.orientation.item,e=this.dom.box;e.style.top="top"==t?this.top+"px":this.parent.height-this.top-this.height+"px"},o.prototype._repaintDragLeft=function(){if((this.selected||this.options.itemsAlwaysDraggable.range)&&this.options.editable.updateTime&&!this.dom.dragLeft){var t=document.createElement("div");t.className="vis-drag-left",t.dragLeftItem=this,this.dom.box.appendChild(t),this.dom.dragLeft=t}else this.selected||this.options.itemsAlwaysDraggable.range||!this.dom.dragLeft||(this.dom.dragLeft.parentNode&&this.dom.dragLeft.parentNode.removeChild(this.dom.dragLeft),this.dom.dragLeft=null)},o.prototype._repaintDragRight=function(){if((this.selected||this.options.itemsAlwaysDraggable.range)&&this.options.editable.updateTime&&!this.dom.dragRight){var t=document.createElement("div");t.className="vis-drag-right",t.dragRightItem=this,this.dom.box.appendChild(t),this.dom.dragRight=t}else this.selected||this.options.itemsAlwaysDraggable.range||!this.dom.dragRight||(this.dom.dragRight.parentNode&&this.dom.dragRight.parentNode.removeChild(this.dom.dragRight),this.dom.dragRight=null)},t.exports=o},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(19),s=o(n),r=i(6),a=o(r),h=i(0),d=o(h),l=i(1),u=o(l),c=i(2),p=i(179).default,f=function(){function t(e,i,o){var n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:1;(0,d.default)(this,t),this.parent=e,this.changedOptions=[],this.container=i,this.allowCreation=!1,this.options={},this.initialized=!1,this.popupCounter=0,this.defaultOptions={enabled:!1,filter:!0,container:void 0,showButton:!0},c.extend(this.options,this.defaultOptions),this.configureOptions=o,this.moduleOptions={},this.domElements=[],this.popupDiv={},this.popupLimit=5,this.popupHistory={},this.colorPicker=new p(n),this.wrapper=void 0}return(0,u.default)(t,[{key:"setOptions",value:function(t){if(void 0!==t){this.popupHistory={},this._removePopup();var e=!0;"string"==typeof t?this.options.filter=t:t instanceof Array?this.options.filter=t.join():"object"===(void 0===t?"undefined":(0,a.default)(t))?(void 0!==t.container&&(this.options.container=t.container),void 0!==t.filter&&(this.options.filter=t.filter),void 0!==t.showButton&&(this.options.showButton=t.showButton),void 0!==t.enabled&&(e=t.enabled)):"boolean"==typeof t?(this.options.filter=!0,e=t):"function"==typeof t&&(this.options.filter=t,e=!0),!1===this.options.filter&&(e=!1),this.options.enabled=e}this._clean()}},{key:"setModuleOptions",value:function(t){this.moduleOptions=t,!0===this.options.enabled&&(this._clean(),void 0!==this.options.container&&(this.container=this.options.container),this._create())}},{key:"_create",value:function(){var t=this;this._clean(),this.changedOptions=[];var e=this.options.filter,i=0,o=!1;for(var n in this.configureOptions)this.configureOptions.hasOwnProperty(n)&&(this.allowCreation=!1,o=!1,"function"==typeof e?(o=e(n,[]),o=o||this._handleObject(this.configureOptions[n],[n],!0)):!0!==e&&-1===e.indexOf(n)||(o=!0),!1!==o&&(this.allowCreation=!0,i>0&&this._makeItem([]),this._makeHeader(n),this._handleObject(this.configureOptions[n],[n])),i++);if(!0===this.options.showButton){var s=document.createElement("div");s.className="vis-configuration vis-config-button",s.innerHTML="generate options",s.onclick=function(){t._printOptions()},s.onmouseover=function(){s.className="vis-configuration vis-config-button hover"},s.onmouseout=function(){s.className="vis-configuration vis-config-button"},this.optionsContainer=document.createElement("div"),this.optionsContainer.className="vis-configuration vis-config-option-container",this.domElements.push(this.optionsContainer),this.domElements.push(s)}this._push()}},{key:"_push",value:function(){this.wrapper=document.createElement("div"),this.wrapper.className="vis-configuration-wrapper",this.container.appendChild(this.wrapper);for(var t=0;t1?i-1:0),n=1;n2&&void 0!==arguments[2]&&arguments[2],o=document.createElement("div");return o.className="vis-configuration vis-config-label vis-config-s"+e.length,o.innerHTML=!0===i?""+t+":":t+":",o}},{key:"_makeDropdown",value:function(t,e,i){var o=document.createElement("select");o.className="vis-configuration vis-config-select";var n=0;void 0!==e&&-1!==t.indexOf(e)&&(n=t.indexOf(e));for(var s=0;ss&&1!==s&&(a.max=Math.ceil(1.2*e),d=a.max,h="range increased"),a.value=e}else a.value=o;var l=document.createElement("input");l.className="vis-configuration vis-config-rangeinput",l.value=a.value;var u=this;a.onchange=function(){l.value=this.value,u._update(Number(this.value),i)},a.oninput=function(){l.value=this.value};var c=this._makeLabel(i[i.length-1],i),p=this._makeItem(i,c,a,l);""!==h&&this.popupHistory[p]!==d&&(this.popupHistory[p]=d,this._setupPopup(h,p))}},{key:"_setupPopup",value:function(t,e){var i=this;if(!0===this.initialized&&!0===this.allowCreation&&this.popupCounter1&&void 0!==arguments[1]?arguments[1]:[],i=arguments.length>2&&void 0!==arguments[2]&&arguments[2],o=!1,n=this.options.filter,s=!1;for(var r in t)if(t.hasOwnProperty(r)){o=!0;var a=t[r],h=c.copyAndExtendArray(e,r);if("function"==typeof n&&!1===(o=n(r,e))&&!(a instanceof Array)&&"string"!=typeof a&&"boolean"!=typeof a&&a instanceof Object&&(this.allowCreation=!1,o=this._handleObject(a,h,!0),this.allowCreation=!1===i),!1!==o){s=!0;var d=this._getValue(h);if(a instanceof Array)this._handleArray(a,d,h);else if("string"==typeof a)this._makeTextInput(a,d,h);else if("boolean"==typeof a)this._makeCheckbox(a,d,h);else if(a instanceof Object){var l=!0;if(-1!==e.indexOf("physics")&&this.moduleOptions.physics.solver!==r&&(l=!1),!0===l)if(void 0!==a.enabled){var u=c.copyAndExtendArray(h,"enabled"),p=this._getValue(u);if(!0===p){var f=this._makeLabel(r,h,!0);this._makeItem(h,f),s=this._handleObject(a,h)||s}else this._makeCheckbox(a,p,h)}else{var m=this._makeLabel(r,h,!0);this._makeItem(h,m),s=this._handleObject(a,h)||s}}else console.error("dont know how to handle",a,r,h)}}return s}},{key:"_handleArray",value:function(t,e,i){"string"==typeof t[0]&&"color"===t[0]?(this._makeColorField(t,e,i),t[1]!==e&&this.changedOptions.push({path:i,value:e})):"string"==typeof t[0]?(this._makeDropdown(t,e,i),t[0]!==e&&this.changedOptions.push({path:i,value:e})):"number"==typeof t[0]&&(this._makeRange(t,e,i),t[0]!==e&&this.changedOptions.push({path:i,value:Number(e)}))}},{key:"_update",value:function(t,e){var i=this._constructOptions(t,e);this.parent.body&&this.parent.body.emitter&&this.parent.body.emitter.emit&&this.parent.body.emitter.emit("configChange",i),this.initialized=!0,this.parent.setOptions(i)}},{key:"_constructOptions",value:function(t,e){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},o=i;t="true"===t||t,t="false"!==t&&t;for(var n=0;nvar options = "+(0,s.default)(t,null,2)+""}},{key:"getOptions",value:function(){for(var t={},e=0;ethis.imageObj.height?i=this.imageObj.width/this.imageObj.height:o=this.imageObj.height/this.imageObj.width),t=2*this.options.size*i,e=2*this.options.size*o}else t=this.imageObj.width,e=this.imageObj.height;this.width=t,this.height=e,this.radius=.5*this.width}},{key:"_drawRawCircle",value:function(t,e,i,o){this.initContextForDraw(t,o),t.circle(e,i,o.size),this.performFill(t,o)}},{key:"_drawImageAtPosition",value:function(t,e){if(0!=this.imageObj.width){t.globalAlpha=1,this.enableShadow(t,e);var i=1;!0===this.options.shapeProperties.interpolation&&(i=this.imageObj.width/this.width/this.body.view.scale),this.imageObj.drawImageAtPosition(t,i,this.left,this.top,this.width,this.height),this.disableShadow(t,e)}}},{key:"_drawImageLabel",value:function(t,e,i,o,n){var s,r=0;if(void 0!==this.height){r=.5*this.height;var a=this.labelModule.getTextSize(t,o,n);a.lineCount>=1&&(r+=a.height/2)}s=i+r,this.options.label&&(this.labelOffset=r),this.labelModule.draw(t,e,s,o,n,"hanging")}}]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(19),s=o(n),r=i(6),a=o(r),h=i(29),d=o(h),l=i(0),u=o(l),c=i(1),p=o(c),f=i(2),m=i(117).default,v=i(48).default,g=i(215).default,y=i(217).default,b=i(218).default,_=i(219).default,w=function(){function t(e,i,o,n){if((0,u.default)(this,t),void 0===i)throw new Error("No body provided");this.options=f.bridgeObject(o),this.globalOptions=o,this.defaultOptions=n,this.body=i,this.id=void 0,this.fromId=void 0,this.toId=void 0,this.selected=!1,this.hover=!1,this.labelDirty=!0,this.baseWidth=this.options.width,this.baseFontSize=this.options.font.size,this.from=void 0,this.to=void 0,this.edgeType=void 0,this.connected=!1,this.labelModule=new m(this.body,this.options,!0),this.setOptions(e)}return(0,p.default)(t,[{key:"setOptions",value:function(e){if(e){t.parseOptions(this.options,e,!0,this.globalOptions),void 0!==e.id&&(this.id=e.id),void 0!==e.from&&(this.fromId=e.from),void 0!==e.to&&(this.toId=e.to),void 0!==e.title&&(this.title=e.title),void 0!==e.value&&(e.value=parseFloat(e.value));var i=[e,this.options,this.defaultOptions];this.chooser=v.choosify("edge",i),this.updateLabelModule(e);var o=this.updateEdgeType();return this._setInteractionWidths(),this.connect(),void 0===e.hidden&&void 0===e.physics||(o=!0),o}}},{key:"getFormattingValues",value:function(){var t=!0===this.options.arrows.to||!0===this.options.arrows.to.enabled,e=!0===this.options.arrows.from||!0===this.options.arrows.from.enabled,i=!0===this.options.arrows.middle||!0===this.options.arrows.middle.enabled,o=this.options.color.inherit,n={toArrow:t,toArrowScale:this.options.arrows.to.scaleFactor,toArrowType:this.options.arrows.to.type,middleArrow:i,middleArrowScale:this.options.arrows.middle.scaleFactor,middleArrowType:this.options.arrows.middle.type,fromArrow:e,fromArrowScale:this.options.arrows.from.scaleFactor,fromArrowType:this.options.arrows.from.type,arrowStrikethrough:this.options.arrowStrikethrough,color:o?void 0:this.options.color.color,inheritsColor:o,opacity:this.options.color.opacity,hidden:this.options.hidden,length:this.options.length,shadow:this.options.shadow.enabled,shadowColor:this.options.shadow.color,shadowSize:this.options.shadow.size,shadowX:this.options.shadow.x,shadowY:this.options.shadow.y,dashes:this.options.dashes,width:this.options.width};if(this.selected||this.hover)if(!0===this.chooser){if(this.selected){var s=this.options.selectionWidth;"function"==typeof s?n.width=s(n.width):"number"==typeof s&&(n.width+=s),n.width=Math.max(n.width,.3/this.body.view.scale),n.color=this.options.color.highlight,n.shadow=this.options.shadow.enabled}else if(this.hover){var r=this.options.hoverWidth;"function"==typeof r?n.width=r(n.width):"number"==typeof r&&(n.width+=r),n.width=Math.max(n.width,.3/this.body.view.scale),n.color=this.options.color.hover,n.shadow=this.options.shadow.enabled}}else"function"==typeof this.chooser&&(this.chooser(n,this.options.id,this.selected,this.hover),void 0!==n.color&&(n.inheritsColor=!1),!1===n.shadow&&(n.shadowColor===this.options.shadow.color&&n.shadowSize===this.options.shadow.size&&n.shadowX===this.options.shadow.x&&n.shadowY===this.options.shadow.y||(n.shadow=!0)));else n.shadow=this.options.shadow.enabled,n.width=Math.max(n.width,.3/this.body.view.scale);return n}},{key:"updateLabelModule",value:function(t){var e=[t,this.options,this.globalOptions,this.defaultOptions];this.labelModule.update(this.options,e),void 0!==this.labelModule.baseSize&&(this.baseFontSize=this.labelModule.baseSize)}},{key:"updateEdgeType",value:function(){var t=this.options.smooth,e=!1,i=!0;return void 0!==this.edgeType&&((this.edgeType instanceof y&&!0===t.enabled&&"dynamic"===t.type||this.edgeType instanceof g&&!0===t.enabled&&"cubicBezier"===t.type||this.edgeType instanceof b&&!0===t.enabled&&"dynamic"!==t.type&&"cubicBezier"!==t.type||this.edgeType instanceof _&&!1===t.type.enabled)&&(i=!1),!0===i&&(e=this.cleanup())),!0===i?!0===t.enabled?"dynamic"===t.type?(e=!0,this.edgeType=new y(this.options,this.body,this.labelModule)):"cubicBezier"===t.type?this.edgeType=new g(this.options,this.body,this.labelModule):this.edgeType=new b(this.options,this.body,this.labelModule):this.edgeType=new _(this.options,this.body,this.labelModule):this.edgeType.setOptions(this.options),e}},{key:"connect",value:function(){this.disconnect(),this.from=this.body.nodes[this.fromId]||void 0,this.to=this.body.nodes[this.toId]||void 0,this.connected=void 0!==this.from&&void 0!==this.to,!0===this.connected?(this.from.attachEdge(this),this.to.attachEdge(this)):(this.from&&this.from.detachEdge(this),this.to&&this.to.detachEdge(this)),this.edgeType.connect()}},{key:"disconnect",value:function(){this.from&&(this.from.detachEdge(this),this.from=void 0),this.to&&(this.to.detachEdge(this),this.to=void 0),this.connected=!1}},{key:"getTitle",value:function(){return this.title}},{key:"isSelected",value:function(){return this.selected}},{key:"getValue",value:function(){return this.options.value}},{key:"setValueRange",value:function(t,e,i){if(void 0!==this.options.value){var o=this.options.scaling.customScalingFunction(t,e,i,this.options.value),n=this.options.scaling.max-this.options.scaling.min;if(!0===this.options.scaling.label.enabled){var s=this.options.scaling.label.max-this.options.scaling.label.min;this.options.font.size=this.options.scaling.label.min+o*s}this.options.width=this.options.scaling.min+o*n}else this.options.width=this.baseWidth,this.options.font.size=this.baseFontSize;this._setInteractionWidths(),this.updateLabelModule()}},{key:"_setInteractionWidths",value:function(){"function"==typeof this.options.hoverWidth?this.edgeType.hoverWidth=this.options.hoverWidth(this.options.width):this.edgeType.hoverWidth=this.options.hoverWidth+this.options.width,"function"==typeof this.options.selectionWidth?this.edgeType.selectionWidth=this.options.selectionWidth(this.options.width):this.edgeType.selectionWidth=this.options.selectionWidth+this.options.width}},{key:"draw",value:function(t){var e=this.getFormattingValues();if(!e.hidden){var i=this.edgeType.getViaNode(),o={};this.edgeType.fromPoint=this.edgeType.from,this.edgeType.toPoint=this.edgeType.to,e.fromArrow&&(o.from=this.edgeType.getArrowData(t,"from",i,this.selected,this.hover,e),!1===e.arrowStrikethrough&&(this.edgeType.fromPoint=o.from.core)),e.toArrow&&(o.to=this.edgeType.getArrowData(t,"to",i,this.selected,this.hover,e),!1===e.arrowStrikethrough&&(this.edgeType.toPoint=o.to.core)),e.middleArrow&&(o.middle=this.edgeType.getArrowData(t,"middle",i,this.selected,this.hover,e)),this.edgeType.drawLine(t,e,this.selected,this.hover,i),this.drawArrows(t,o,e),this.drawLabel(t,i)}}},{key:"drawArrows",value:function(t,e,i){i.fromArrow&&this.edgeType.drawArrowHead(t,i,this.selected,this.hover,e.from),i.middleArrow&&this.edgeType.drawArrowHead(t,i,this.selected,this.hover,e.middle),i.toArrow&&this.edgeType.drawArrowHead(t,i,this.selected,this.hover,e.to)}},{key:"drawLabel",value:function(t,e){if(void 0!==this.options.label){var i=this.from,o=this.to;if(this.labelModule.differentState(this.selected,this.hover)&&this.labelModule.getTextSize(t,this.selected,this.hover),i.id!=o.id){this.labelModule.pointToSelf=!1;var n=this.edgeType.getPoint(.5,e);t.save();var s=this._getRotation(t);0!=s.angle&&(t.translate(s.x,s.y),t.rotate(s.angle)),this.labelModule.draw(t,n.x,n.y,this.selected,this.hover),t.restore()}else{this.labelModule.pointToSelf=!0;var r,a,h=this.options.selfReferenceSize;i.shape.width>i.shape.height?(r=i.x+.5*i.shape.width,a=i.y-h):(r=i.x+h,a=i.y-.5*i.shape.height),n=this._pointOnCircle(r,a,h,.125),this.labelModule.draw(t,n.x,n.y,this.selected,this.hover)}}}},{key:"getItemsOnPoint",value:function(t){var e=[];if(this.labelModule.visible()){var i=this._getRotation();v.pointInRect(this.labelModule.getSize(),t,i)&&e.push({edgeId:this.id,labelId:0})}var o={left:t.x,top:t.y};return this.isOverlappingWith(o)&&e.push({edgeId:this.id}),e}},{key:"isOverlappingWith",value:function(t){if(this.connected){var e=this.from.x,i=this.from.y,o=this.to.x,n=this.to.y,s=t.left,r=t.top;return this.edgeType.getDistanceToEdge(e,i,o,n,s,r)<10}return!1}},{key:"_getRotation",value:function(t){var e=this.edgeType.getViaNode(),i=this.edgeType.getPoint(.5,e);void 0!==t&&this.labelModule.calculateLabelSize(t,this.selected,this.hover,i.x,i.y);var o={x:i.x,y:this.labelModule.size.yLine,angle:0};if(!this.labelModule.visible())return o;if("horizontal"===this.options.font.align)return o;var n=this.from.y-this.to.y,s=this.from.x-this.to.x,r=Math.atan2(n,s);return(r<-1&&s<0||r>0&&s<0)&&(r+=Math.PI),o.angle=r,o}},{key:"_pointOnCircle",value:function(t,e,i,o){var n=2*o*Math.PI;return{x:t+i*Math.cos(n),y:e-i*Math.sin(n)}}},{key:"select",value:function(){this.selected=!0}},{key:"unselect",value:function(){this.selected=!1}},{key:"cleanup",value:function(){return this.edgeType.cleanup()}},{key:"remove",value:function(){this.cleanup(),this.disconnect(),delete this.body.edges[this.id]}},{key:"endPointsValid",value:function(){return void 0!==this.body.nodes[this.fromId]&&void 0!==this.body.nodes[this.toId]}}],[{key:"parseOptions",value:function(t,e){var i=arguments.length>2&&void 0!==arguments[2]&&arguments[2],o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},n=arguments.length>4&&void 0!==arguments[4]&&arguments[4],r=["arrowStrikethrough","id","from","hidden","hoverWidth","labelHighlightBold","length","line","opacity","physics","scaling","selectionWidth","selfReferenceSize","to","title","value","width","font","chosen","widthConstraint"];if(f.selectiveDeepExtend(r,t,e,i),v.isValidLabel(e.label)?t.label=e.label:t.label=void 0,f.mergeOptions(t,e,"smooth",o),f.mergeOptions(t,e,"shadow",o),void 0!==e.dashes&&null!==e.dashes?t.dashes=e.dashes:!0===i&&null===e.dashes&&(t.dashes=(0,d.default)(o.dashes)),void 0!==e.scaling&&null!==e.scaling?(void 0!==e.scaling.min&&(t.scaling.min=e.scaling.min),void 0!==e.scaling.max&&(t.scaling.max=e.scaling.max),f.mergeOptions(t.scaling,e.scaling,"label",o.scaling)):!0===i&&null===e.scaling&&(t.scaling=(0,d.default)(o.scaling)),void 0!==e.arrows&&null!==e.arrows)if("string"==typeof e.arrows){var h=e.arrows.toLowerCase();t.arrows.to.enabled=-1!=h.indexOf("to"),t.arrows.middle.enabled=-1!=h.indexOf("middle"),t.arrows.from.enabled=-1!=h.indexOf("from")}else{if("object"!==(0,a.default)(e.arrows))throw new Error("The arrow newOptions can only be an object or a string. Refer to the documentation. You used:"+(0,s.default)(e.arrows));f.mergeOptions(t.arrows,e.arrows,"to",o.arrows),f.mergeOptions(t.arrows,e.arrows,"middle",o.arrows),f.mergeOptions(t.arrows,e.arrows,"from",o.arrows)}else!0===i&&null===e.arrows&&(t.arrows=(0,d.default)(o.arrows));if(void 0!==e.color&&null!==e.color){var l=e.color,u=t.color;if(n)f.deepExtend(u,o.color,!1,i);else for(var c in u)u.hasOwnProperty(c)&&delete u[c];if(f.isString(u))u.color=u,u.highlight=u,u.hover=u,u.inherit=!1,void 0===l.opacity&&(u.opacity=1);else{var p=!1;void 0!==l.color&&(u.color=l.color,p=!0),void 0!==l.highlight&&(u.highlight=l.highlight,p=!0),void 0!==l.hover&&(u.hover=l.hover,p=!0),void 0!==l.inherit&&(u.inherit=l.inherit),void 0!==l.opacity&&(u.opacity=Math.min(1,Math.max(0,l.opacity))),!0===p?u.inherit=!1:void 0===u.inherit&&(u.inherit="from")}}else!0===i&&null===e.color&&(t.color=f.bridgeObject(o.color));!0===i&&null===e.font&&(t.font=f.bridgeObject(o.font))}}]),t}();e.default=w},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(118),m=o(f),v=function(t){function e(t,i,o){return(0,a.default)(this,e),(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o))}return(0,p.default)(e,t),(0,d.default)(e,[{key:"_findBorderPositionBezier",value:function(t,e){var i,o,n,s,r,a=arguments.length>2&&void 0!==arguments[2]?arguments[2]:this._getViaCoordinates(),h=0,d=0,l=1,u=this.to,c=!1;for(t.id===this.from.id&&(u=this.from,c=!0);d<=l&&h<10;){var p=.5*(d+l);if(i=this.getPoint(p,a),o=Math.atan2(u.y-i.y,u.x-i.x),n=u.distanceToBorder(e,o),s=Math.sqrt(Math.pow(i.x-u.x,2)+Math.pow(i.y-u.y,2)),r=n-s,Math.abs(r)<.2)break;r<0?!1===c?d=p:l=p:!1===c?l=p:d=p,h++}return i.t=p,i}},{key:"_getDistanceToBezierEdge",value:function(t,e,i,o,n,s,r){var a=1e9,h=void 0,d=void 0,l=void 0,u=void 0,c=void 0,p=t,f=e;for(d=1;d<10;d++)l=.1*d,u=Math.pow(1-l,2)*t+2*l*(1-l)*r.x+Math.pow(l,2)*i,c=Math.pow(1-l,2)*e+2*l*(1-l)*r.y+Math.pow(l,2)*o,d>0&&(h=this._getDistanceToLine(p,f,u,c,n,s),a=h1&&void 0!==arguments[1]?arguments[1]:[],o=1e9,n=-1e9,s=1e9,r=-1e9;if(i.length>0)for(var a=0;ae.shape.boundingBox.left&&(s=e.shape.boundingBox.left),re.shape.boundingBox.top&&(o=e.shape.boundingBox.top),n1&&void 0!==arguments[1]?arguments[1]:[],o=1e9,n=-1e9,s=1e9,r=-1e9;if(i.length>0)for(var a=0;ae.x&&(s=e.x),re.y&&(o=e.y),nh;)o(a,i=e[h++])&&(~s(d,i)||d.push(i));return d}},function(t,e,i){var o=i(22),n=i(41),s=i(56)("IE_PROTO"),r=Object.prototype;t.exports=Object.getPrototypeOf||function(t){return t=n(t),o(t,s)?t[s]:"function"==typeof t.constructor&&t instanceof t.constructor?t.constructor.prototype:t instanceof Object?r:null}},function(t,e,i){var o=i(50),n=i(13)("toStringTag"),s="Arguments"==o(function(){return arguments}()),r=function(t,e){try{return t[e]}catch(t){}};t.exports=function(t){var e,i,a;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(i=r(e=Object(t),n))?i:s?o(e):"Object"==(a=o(e))&&"function"==typeof e.callee?"Arguments":a}},function(t,e,i){var o=i(17),n=i(7),s=i(28);t.exports=function(t,e){var i=(n.Object||{})[t]||Object[t],r={};r[t]=e(i),o(o.S+o.F*s(function(){i(1)}),"Object",r)}},function(t,e,i){var o=i(84),n=i(58).concat("length","prototype");e.f=Object.getOwnPropertyNames||function(t){return o(t,n)}},function(t,e,i){var o=i(42),n=i(39),s=i(25),r=i(53),a=i(22),h=i(81),d=Object.getOwnPropertyDescriptor;e.f=i(21)?d:function(t,e){if(t=s(t),e=r(e,!0),h)try{return d(t,e)}catch(t){}if(a(t,e))return n(!o.f.call(t,e),t[e])}},function(t,e,i){t.exports={default:i(162),__esModule:!0}},function(t,e,i){function o(t,e){this.x=void 0!==t?t:0,this.y=void 0!==e?e:0}t.exports=o},function(t,e,i){function o(t,e){if(void 0===t)throw new Error("No container element defined");if(this.container=t,this.visible=!e||void 0==e.visible||e.visible,this.visible){this.frame=document.createElement("DIV"),this.frame.style.width="100%",this.frame.style.position="relative",this.container.appendChild(this.frame),this.frame.prev=document.createElement("INPUT"),this.frame.prev.type="BUTTON",this.frame.prev.value="Prev",this.frame.appendChild(this.frame.prev),this.frame.play=document.createElement("INPUT"),this.frame.play.type="BUTTON",this.frame.play.value="Play",this.frame.appendChild(this.frame.play),this.frame.next=document.createElement("INPUT"),this.frame.next.type="BUTTON",this.frame.next.value="Next",this.frame.appendChild(this.frame.next),this.frame.bar=document.createElement("INPUT"),this.frame.bar.type="BUTTON",this.frame.bar.style.position="absolute",this.frame.bar.style.border="1px solid red",this.frame.bar.style.width="100px",this.frame.bar.style.height="6px",this.frame.bar.style.borderRadius="2px",this.frame.bar.style.MozBorderRadius="2px",this.frame.bar.style.border="1px solid #7F7F7F",this.frame.bar.style.backgroundColor="#E5E5E5",this.frame.appendChild(this.frame.bar),this.frame.slide=document.createElement("INPUT"),this.frame.slide.type="BUTTON",this.frame.slide.style.margin="0px",this.frame.slide.value=" ",this.frame.slide.style.position="relative",this.frame.slide.style.left="-100px",this.frame.appendChild(this.frame.slide);var i=this;this.frame.slide.onmousedown=function(t){i._onMouseDown(t)},this.frame.prev.onclick=function(t){i.prev(t)},this.frame.play.onclick=function(t){i.togglePlay(t)},this.frame.next.onclick=function(t){i.next(t)}}this.onChangeCallback=void 0,this.values=[],this.index=void 0,this.playTimeout=void 0,this.playInterval=1e3,this.playLoop=!0}var n=i(2);o.prototype.prev=function(){var t=this.getIndex();t>0&&(t--,this.setIndex(t))},o.prototype.next=function(){var t=this.getIndex();t0?this.setIndex(0):this.index=void 0},o.prototype.setIndex=function(t){if(!(tthis.values.length-1&&(o=this.values.length-1),o},o.prototype.indexToLeft=function(t){var e=parseFloat(this.frame.bar.style.width)-this.frame.slide.clientWidth-10;return t/(this.values.length-1)*e+3},o.prototype._onMouseMove=function(t){var e=t.clientX-this.startClientX,i=this.startSlideX+e,o=this.leftToIndex(i);this.setIndex(o),n.preventDefault()},o.prototype._onMouseUp=function(t){this.frame.style.cursor="auto",n.removeEventListener(document,"mousemove",this.onmousemove),n.removeEventListener(document,"mouseup",this.onmouseup),n.preventDefault()},t.exports=o},function(t,e,i){function o(t,e,i,o){this._start=0,this._end=0,this._step=1,this.prettyStep=!0,this.precision=5,this._current=0,this.setRange(t,e,i,o)}o.prototype.isNumeric=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},o.prototype.setRange=function(t,e,i,o){if(!this.isNumeric(t))throw new Error("Parameter 'start' is not numeric; value: "+t);if(!this.isNumeric(e))throw new Error("Parameter 'end' is not numeric; value: "+t);if(!this.isNumeric(i))throw new Error("Parameter 'step' is not numeric; value: "+t);this._start=t||0,this._end=e||0,this.setStep(i,o)},o.prototype.setStep=function(t,e){void 0===t||t<=0||(void 0!==e&&(this.prettyStep=e),!0===this.prettyStep?this._step=o.calculatePrettyStep(t):this._step=t)},o.calculatePrettyStep=function(t){var e=function(t){return Math.log(t)/Math.LN10},i=Math.pow(10,Math.round(e(t))),o=2*Math.pow(10,Math.round(e(t/2))),n=5*Math.pow(10,Math.round(e(t/5))),s=i;return Math.abs(o-t)<=Math.abs(s-t)&&(s=o),Math.abs(n-t)<=Math.abs(s-t)&&(s=n),s<=0&&(s=1),s},o.prototype.getCurrent=function(){return parseFloat(this._current.toPrecision(this.precision))},o.prototype.getStep=function(){return this._step},o.prototype.start=function(t){void 0===t&&(t=!1),this._current=this._start-this._start%this._step,t&&this.getCurrent()this._end},t.exports=o},function(t,e,i){function o(t){for(var e in t)if(t.hasOwnProperty(e))return!1;return!0}function n(t){return void 0===t||""===t||"string"!=typeof t?t:t.charAt(0).toUpperCase()+t.slice(1)}function s(t,e){return void 0===t||""===t?e:t+n(e)}function r(t,e,i,o){for(var n,r,a=0;ar&&(t=o(t)*r),i(e)>r&&(e=o(e)*r),this.cameraOffset.x=t,this.cameraOffset.y=e,this.calculateCameraOrientation()},o.prototype.getOffset=function(){return this.cameraOffset},o.prototype.setArmLocation=function(t,e,i){this.armLocation.x=t,this.armLocation.y=e,this.armLocation.z=i,this.calculateCameraOrientation()},o.prototype.setArmRotation=function(t,e){void 0!==t&&(this.armRotation.horizontal=t),void 0!==e&&(this.armRotation.vertical=e,this.armRotation.vertical<0&&(this.armRotation.vertical=0),this.armRotation.vertical>.5*Math.PI&&(this.armRotation.vertical=.5*Math.PI)),void 0===t&&void 0===e||this.calculateCameraOrientation()},o.prototype.getArmRotation=function(){var t={};return t.horizontal=this.armRotation.horizontal,t.vertical=this.armRotation.vertical,t},o.prototype.setArmLength=function(t){void 0!==t&&(this.armLength=t,this.armLength<.71&&(this.armLength=.71),this.armLength>5&&(this.armLength=5),this.setOffset(this.cameraOffset.x,this.cameraOffset.y),this.calculateCameraOrientation())}, @@ -36,12 +36,12 @@ value:function(t,e,i,o,n){this.enableShadow(t,e),t.beginPath(),t.arc(i,o,n,0,2*M ;for(var a in arguments[0])n+=a+": "+arguments[0][a]+", ";n=n.slice(0,-2)}else n=arguments[r];s.push(n)}x(t+"\nArguments: "+Array.prototype.slice.call(s).join("")+"\n"+(new Error).stack),o=!1}return i.apply(this,arguments)},i)}function S(t,i){null!=e.deprecationHandler&&e.deprecationHandler(t,i),Po[t]||(x(i),Po[t]=!0)}function D(t){return t instanceof Function||"[object Function]"===Object.prototype.toString.call(t)}function M(t){var e,i;for(i in t)e=t[i],D(e)?this[i]=e:this["_"+i]=e;this._config=t,this._dayOfMonthOrdinalParseLenient=new RegExp((this._dayOfMonthOrdinalParse.source||this._ordinalParse.source)+"|"+/\d{1,2}/.source)}function C(t,e){var i,n=l({},t);for(i in e)d(e,i)&&(o(t[i])&&o(e[i])?(n[i]={},l(n[i],t[i]),l(n[i],e[i])):null!=e[i]?n[i]=e[i]:delete n[i]);for(i in t)d(t,i)&&!d(e,i)&&o(t[i])&&(n[i]=l({},n[i]));return n}function O(t){null!=t&&this.set(t)}function E(t,e,i){var o=this._calendar[t]||this._calendar.sameElse;return D(o)?o.call(e,i):o}function T(t){var e=this._longDateFormat[t],i=this._longDateFormat[t.toUpperCase()];return e||!i?e:(this._longDateFormat[t]=i.replace(/MMMM|MM|DD|dddd/g,function(t){return t.slice(1)}),this._longDateFormat[t])}function P(){return this._invalidDate}function I(t){return this._ordinal.replace("%d",t)}function N(t,e,i,o){var n=this._relativeTime[i];return D(n)?n(t,e,i,o):n.replace(/%d/i,t)}function R(t,e){var i=this._relativeTime[t>0?"future":"past"];return D(i)?i(e):i.replace(/%s/i,e)}function A(t,e){var i=t.toLowerCase();Lo[i]=Lo[i+"s"]=Lo[e]=t}function z(t){return"string"==typeof t?Lo[t]||Lo[t.toLowerCase()]:void 0}function L(t){var e,i,o={};for(i in t)d(t,i)&&(e=z(i))&&(o[e]=t[i]);return o}function F(t,e){Fo[t]=e}function B(t){var e=[];for(var i in t)e.push({unit:i,priority:Fo[i]});return e.sort(function(t,e){return t.priority-e.priority}),e}function j(t,e,i){var o=""+Math.abs(t),n=e-o.length;return(t>=0?i?"+":"":"-")+Math.pow(10,Math.max(0,n)).toString().substr(1)+o}function H(t,e,i,o){var n=o;"string"==typeof o&&(n=function(){return this[o]()}),t&&(Wo[t]=n),e&&(Wo[e[0]]=function(){return j(n.apply(this,arguments),e[1],e[2])}),i&&(Wo[i]=function(){return this.localeData().ordinal(n.apply(this,arguments),t)})}function W(t){return t.match(/\[[\s\S]/)?t.replace(/^\[|\]$/g,""):t.replace(/\\/g,"")}function Y(t){var e,i,o=t.match(Bo);for(e=0,i=o.length;e=0&&jo.test(t);)t=t.replace(jo,i),jo.lastIndex=0,o-=1;return t}function U(t,e,i){an[t]=D(e)?e:function(t,o){return t&&i?i:e}}function q(t,e){return d(an,t)?an[t](e._strict,e._locale):new RegExp(X(t))}function X(t){return Z(t.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(t,e,i,o,n){return e||i||o||n}))}function Z(t){return t.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function K(t,e){var i,o=e;for("string"==typeof t&&(t=[t]),r(e)&&(o=function(t,i){i[e]=_(t)}),i=0;i=0&&isFinite(a.getFullYear())&&a.setFullYear(t),a}function _t(t){var e=new Date(Date.UTC.apply(null,arguments));return t<100&&t>=0&&isFinite(e.getUTCFullYear())&&e.setUTCFullYear(t),e}function wt(t,e,i){var o=7+e-i;return-(7+_t(t,0,o).getUTCDay()-e)%7+o-1}function xt(t,e,i,o,n){var s,r,a=(7+i-o)%7,h=wt(t,o,n),d=1+7*(e-1)+a+h;return d<=0?(s=t-1,r=Q(s)+d):d>Q(t)?(s=t+1,r=d-Q(t)):(s=t,r=d),{year:s,dayOfYear:r}}function kt(t,e,i){var o,n,s=wt(t.year(),e,i),r=Math.floor((t.dayOfYear()-s-1)/7)+1;return r<1?(n=t.year()-1,o=r+St(n,e,i)):r>St(t.year(),e,i)?(o=r-St(t.year(),e,i),n=t.year()+1):(n=t.year(),o=r),{week:o,year:n}}function St(t,e,i){var o=wt(t,e,i),n=wt(t+1,e,i);return(Q(t)-o+n)/7}function Dt(t){return kt(t,this._week.dow,this._week.doy).week}function Mt(){return this._week.dow}function Ct(){return this._week.doy}function Ot(t){var e=this.localeData().week(this);return null==t?e:this.add(7*(t-e),"d")}function Et(t){var e=kt(this,1,4).week;return null==t?e:this.add(7*(t-e),"d")}function Tt(t,e){return"string"!=typeof t?t:isNaN(t)?(t=e.weekdaysParse(t),"number"==typeof t?t:null):parseInt(t,10)}function Pt(t,e){return"string"==typeof t?e.weekdaysParse(t)%7||7:isNaN(t)?null:t}function It(t,e){return t?i(this._weekdays)?this._weekdays[t.day()]:this._weekdays[this._weekdays.isFormat.test(e)?"format":"standalone"][t.day()]:i(this._weekdays)?this._weekdays:this._weekdays.standalone}function Nt(t){return t?this._weekdaysShort[t.day()]:this._weekdaysShort}function Rt(t){return t?this._weekdaysMin[t.day()]:this._weekdaysMin}function At(t,e,i){var o,n,s,r=t.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],o=0;o<7;++o)s=u([2e3,1]).day(o),this._minWeekdaysParse[o]=this.weekdaysMin(s,"").toLocaleLowerCase(),this._shortWeekdaysParse[o]=this.weekdaysShort(s,"").toLocaleLowerCase(),this._weekdaysParse[o]=this.weekdays(s,"").toLocaleLowerCase();return i?"dddd"===e?(n=yn.call(this._weekdaysParse,r),-1!==n?n:null):"ddd"===e?(n=yn.call(this._shortWeekdaysParse,r),-1!==n?n:null):(n=yn.call(this._minWeekdaysParse,r),-1!==n?n:null):"dddd"===e?-1!==(n=yn.call(this._weekdaysParse,r))?n:-1!==(n=yn.call(this._shortWeekdaysParse,r))?n:(n=yn.call(this._minWeekdaysParse,r),-1!==n?n:null):"ddd"===e?-1!==(n=yn.call(this._shortWeekdaysParse,r))?n:-1!==(n=yn.call(this._weekdaysParse,r))?n:(n=yn.call(this._minWeekdaysParse,r),-1!==n?n:null):-1!==(n=yn.call(this._minWeekdaysParse,r))?n:-1!==(n=yn.call(this._weekdaysParse,r))?n:(n=yn.call(this._shortWeekdaysParse,r),-1!==n?n:null)}function zt(t,e,i){var o,n,s;if(this._weekdaysParseExact)return At.call(this,t,e,i);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),o=0;o<7;o++){if(n=u([2e3,1]).day(o),i&&!this._fullWeekdaysParse[o]&&(this._fullWeekdaysParse[o]=new RegExp("^"+this.weekdays(n,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[o]=new RegExp("^"+this.weekdaysShort(n,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[o]=new RegExp("^"+this.weekdaysMin(n,"").replace(".",".?")+"$","i")),this._weekdaysParse[o]||(s="^"+this.weekdays(n,"")+"|^"+this.weekdaysShort(n,"")+"|^"+this.weekdaysMin(n,""),this._weekdaysParse[o]=new RegExp(s.replace(".",""),"i")),i&&"dddd"===e&&this._fullWeekdaysParse[o].test(t))return o;if(i&&"ddd"===e&&this._shortWeekdaysParse[o].test(t))return o;if(i&&"dd"===e&&this._minWeekdaysParse[o].test(t))return o;if(!i&&this._weekdaysParse[o].test(t))return o}}function Lt(t){if(!this.isValid())return null!=t?this:NaN;var e=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=t?(t=Tt(t,this.localeData()),this.add(t-e,"d")):e}function Ft(t){if(!this.isValid())return null!=t?this:NaN;var e=(this.day()+7-this.localeData()._week.dow)%7;return null==t?e:this.add(t-e,"d")}function Bt(t){if(!this.isValid())return null!=t?this:NaN;if(null!=t){var e=Pt(t,this.localeData());return this.day(this.day()%7?e:e-7)}return this.day()||7}function jt(t){return this._weekdaysParseExact?(d(this,"_weekdaysRegex")||Yt.call(this),t?this._weekdaysStrictRegex:this._weekdaysRegex):(d(this,"_weekdaysRegex")||(this._weekdaysRegex=En),this._weekdaysStrictRegex&&t?this._weekdaysStrictRegex:this._weekdaysRegex)}function Ht(t){return this._weekdaysParseExact?(d(this,"_weekdaysRegex")||Yt.call(this),t?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(d(this,"_weekdaysShortRegex")||(this._weekdaysShortRegex=Tn),this._weekdaysShortStrictRegex&&t?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)}function Wt(t){return this._weekdaysParseExact?(d(this,"_weekdaysRegex")||Yt.call(this),t?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(d(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=Pn),this._weekdaysMinStrictRegex&&t?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)}function Yt(){function t(t,e){return e.length-t.length}var e,i,o,n,s,r=[],a=[],h=[],d=[];for(e=0;e<7;e++)i=u([2e3,1]).day(e),o=this.weekdaysMin(i,""),n=this.weekdaysShort(i,""),s=this.weekdays(i,""),r.push(o),a.push(n),h.push(s),d.push(o),d.push(n),d.push(s);for(r.sort(t),a.sort(t),h.sort(t),d.sort(t),e=0;e<7;e++)a[e]=Z(a[e]),h[e]=Z(h[e]),d[e]=Z(d[e]);this._weekdaysRegex=new RegExp("^("+d.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+h.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+a.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+r.join("|")+")","i")}function Gt(){return this.hours()%12||12}function Vt(){return this.hours()||24}function Ut(t,e){H(t,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),e)})}function qt(t,e){return e._meridiemParse}function Xt(t){return"p"===(t+"").toLowerCase().charAt(0)}function Zt(t,e,i){return t>11?i?"pm":"PM":i?"am":"AM"}function Kt(t){return t?t.toLowerCase().replace("_","-"):t}function Jt(t){for(var e,i,o,n,s=0;s0;){if(o=$t(n.slice(0,e).join("-")))return o;if(i&&i.length>=e&&w(n,i,!0)>=e-1)break;e--}s++}return null}function $t(e){var i=null;if(!zn[e]&&void 0!==t&&t&&t.exports)try{i=In._abbr;!function(){var t=new Error('Cannot find module "./locale"');throw t.code="MODULE_NOT_FOUND",t}(),Qt(i)}catch(t){}return zn[e]}function Qt(t,e){var i;return t&&(i=s(e)?ie(t):te(t,e))&&(In=i),In._abbr}function te(t,e){if(null!==e){var i=An;if(e.abbr=t,null!=zn[t])S("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),i=zn[t]._config;else if(null!=e.parentLocale){if(null==zn[e.parentLocale])return Ln[e.parentLocale]||(Ln[e.parentLocale]=[]),Ln[e.parentLocale].push({name:t,config:e}),null;i=zn[e.parentLocale]._config}return zn[t]=new O(C(i,e)),Ln[t]&&Ln[t].forEach(function(t){te(t.name,t.config)}),Qt(t),zn[t]}return delete zn[t],null}function ee(t,e){if(null!=e){var i,o=An;null!=zn[t]&&(o=zn[t]._config),e=C(o,e),i=new O(e),i.parentLocale=zn[t],zn[t]=i,Qt(t)}else null!=zn[t]&&(null!=zn[t].parentLocale?zn[t]=zn[t].parentLocale:null!=zn[t]&&delete zn[t]);return zn[t]}function ie(t){var e;if(t&&t._locale&&t._locale._abbr&&(t=t._locale._abbr),!t)return In;if(!i(t)){if(e=$t(t))return e;t=[t]}return Jt(t)}function oe(){return Io(zn)}function ne(t){var e,i=t._a;return i&&-2===p(t).overflow&&(e=i[ln]<0||i[ln]>11?ln:i[un]<1||i[un]>ht(i[dn],i[ln])?un:i[cn]<0||i[cn]>24||24===i[cn]&&(0!==i[pn]||0!==i[fn]||0!==i[mn])?cn:i[pn]<0||i[pn]>59?pn:i[fn]<0||i[fn]>59?fn:i[mn]<0||i[mn]>999?mn:-1,p(t)._overflowDayOfYear&&(eun)&&(e=un),p(t)._overflowWeeks&&-1===e&&(e=vn),p(t)._overflowWeekday&&-1===e&&(e=gn),p(t).overflow=e),t}function se(t,e,i){return null!=t?t:null!=e?e:i}function re(t){var i=new Date(e.now());return t._useUTC?[i.getUTCFullYear(),i.getUTCMonth(),i.getUTCDate()]:[i.getFullYear(),i.getMonth(),i.getDate()]}function ae(t){var e,i,o,n,s=[];if(!t._d){for(o=re(t),t._w&&null==t._a[un]&&null==t._a[ln]&&he(t),null!=t._dayOfYear&&(n=se(t._a[dn],o[dn]),(t._dayOfYear>Q(n)||0===t._dayOfYear)&&(p(t)._overflowDayOfYear=!0),i=_t(n,0,t._dayOfYear),t._a[ln]=i.getUTCMonth(),t._a[un]=i.getUTCDate()),e=0;e<3&&null==t._a[e];++e)t._a[e]=s[e]=o[e];for(;e<7;e++)t._a[e]=s[e]=null==t._a[e]?2===e?1:0:t._a[e];24===t._a[cn]&&0===t._a[pn]&&0===t._a[fn]&&0===t._a[mn]&&(t._nextDay=!0,t._a[cn]=0),t._d=(t._useUTC?_t:bt).apply(null,s),null!=t._tzm&&t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),t._nextDay&&(t._a[cn]=24),t._w&&void 0!==t._w.d&&t._w.d!==t._d.getDay()&&(p(t).weekdayMismatch=!0)}}function he(t){var e,i,o,n,s,r,a,h;if(e=t._w,null!=e.GG||null!=e.W||null!=e.E)s=1,r=4,i=se(e.GG,t._a[dn],kt(De(),1,4).year),o=se(e.W,1),((n=se(e.E,1))<1||n>7)&&(h=!0);else{s=t._locale._week.dow,r=t._locale._week.doy;var d=kt(De(),s,r);i=se(e.gg,t._a[dn],d.year),o=se(e.w,d.week),null!=e.d?((n=e.d)<0||n>6)&&(h=!0):null!=e.e?(n=e.e+s,(e.e<0||e.e>6)&&(h=!0)):n=s}o<1||o>St(i,s,r)?p(t)._overflowWeeks=!0:null!=h?p(t)._overflowWeekday=!0:(a=xt(i,o,n,s,r),t._a[dn]=a.year,t._dayOfYear=a.dayOfYear)}function de(t){var e,i,o,n,s,r,a=t._i,h=Fn.exec(a)||Bn.exec(a);if(h){for(p(t).iso=!0,e=0,i=Hn.length;e0&&p(t).unusedInput.push(r),a=a.slice(a.indexOf(o)+o.length),d+=o.length),Wo[s]?(o?p(t).empty=!1:p(t).unusedTokens.push(s),$(s,o,t)):t._strict&&!o&&p(t).unusedTokens.push(s);p(t).charsLeftOver=h-d,a.length>0&&p(t).unusedInput.push(a),t._a[cn]<=12&&!0===p(t).bigHour&&t._a[cn]>0&&(p(t).bigHour=void 0),p(t).parsedDateParts=t._a.slice(0),p(t).meridiem=t._meridiem,t._a[cn]=ye(t._locale,t._a[cn],t._meridiem),ae(t),ne(t)}function ye(t,e,i){var o;return null==i?e:null!=t.meridiemHour?t.meridiemHour(e,i):null!=t.isPM?(o=t.isPM(i),o&&e<12&&(e+=12),o||12!==e||(e=0),e):e}function be(t){var e,i,o,n,s;if(0===t._f.length)return p(t).invalidFormat=!0,void(t._d=new Date(NaN));for(n=0;nthis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Ue(){if(!s(this._isDSTShifted))return this._isDSTShifted;var t={};if(v(t,this),t=xe(t),t._a){var e=t._isUTC?u(t._a):De(t._a);this._isDSTShifted=this.isValid()&&w(t._a,e.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function qe(){return!!this.isValid()&&!this._isUTC}function Xe(){return!!this.isValid()&&this._isUTC}function Ze(){return!!this.isValid()&&(this._isUTC&&0===this._offset)}function Ke(t,e){var i,o,n,s=t,a=null;return Ne(t)?s={ms:t._milliseconds,d:t._days,M:t._months}:r(t)?(s={},e?s[e]=t:s.milliseconds=t):(a=Jn.exec(t))?(i="-"===a[1]?-1:1,s={y:0,d:_(a[un])*i,h:_(a[cn])*i,m:_(a[pn])*i,s:_(a[fn])*i,ms:_(Re(1e3*a[mn]))*i}):(a=$n.exec(t))?(i="-"===a[1]?-1:(a[1],1),s={y:Je(a[2],i),M:Je(a[3],i),w:Je(a[4],i),d:Je(a[5],i),h:Je(a[6],i),m:Je(a[7],i),s:Je(a[8],i)}):null==s?s={}:"object"==typeof s&&("from"in s||"to"in s)&&(n=Qe(De(s.from),De(s.to)),s={},s.ms=n.milliseconds,s.M=n.months),o=new Ie(s),Ne(t)&&d(t,"_locale")&&(o._locale=t._locale),o}function Je(t,e){var i=t&&parseFloat(t.replace(",","."));return(isNaN(i)?0:i)*e}function $e(t,e){var i={milliseconds:0,months:0};return i.months=e.month()-t.month()+12*(e.year()-t.year()),t.clone().add(i.months,"M").isAfter(e)&&--i.months,i.milliseconds=+e-+t.clone().add(i.months,"M"),i}function Qe(t,e){var i;return t.isValid()&&e.isValid()?(e=Le(e,t),t.isBefore(e)?i=$e(t,e):(i=$e(e,t),i.milliseconds=-i.milliseconds,i.months=-i.months),i):{milliseconds:0,months:0}}function ti(t,e){return function(i,o){var n,s;return null===o||isNaN(+o)||(S(e,"moment()."+e+"(period, number) is deprecated. Please use moment()."+e+"(number, period). See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info."),s=i,i=o,o=s),i="string"==typeof i?+i:i,n=Ke(i,o),ei(this,n,t),this}}function ei(t,i,o,n){var s=i._milliseconds,r=Re(i._days),a=Re(i._months);t.isValid()&&(n=null==n||n,a&&pt(t,ot(t,"Month")+a*o),r&&nt(t,"Date",ot(t,"Date")+r*o),s&&t._d.setTime(t._d.valueOf()+s*o),n&&e.updateOffset(t,r||a))}function ii(t,e){var i=t.diff(e,"days",!0);return i<-6?"sameElse":i<-1?"lastWeek":i<0?"lastDay":i<1?"sameDay":i<2?"nextDay":i<7?"nextWeek":"sameElse"}function oi(t,i){var o=t||De(),n=Le(o,this).startOf("day"),s=e.calendarFormat(this,n)||"sameElse",r=i&&(D(i[s])?i[s].call(this,o):i[s]);return this.format(r||this.localeData().calendar(s,this,De(o)))}function ni(){return new g(this)}function si(t,e){var i=y(t)?t:De(t);return!(!this.isValid()||!i.isValid())&&(e=z(s(e)?"millisecond":e),"millisecond"===e?this.valueOf()>i.valueOf():i.valueOf()9999?G(t,"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]"):D(Date.prototype.toISOString)?this.toDate().toISOString():G(t,"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]")}function mi(){if(!this.isValid())return"moment.invalid(/* "+this._i+" */)";var t="moment",e="";this.isLocal()||(t=0===this.utcOffset()?"moment.utc":"moment.parseZone",e="Z");var i="["+t+'("]',o=0<=this.year()&&this.year()<=9999?"YYYY":"YYYYYY",n=e+'[")]';return this.format(i+o+"-MM-DD[T]HH:mm:ss.SSS"+n)}function vi(t){t||(t=this.isUtc()?e.defaultFormatUtc:e.defaultFormat);var i=G(this,t);return this.localeData().postformat(i)}function gi(t,e){return this.isValid()&&(y(t)&&t.isValid()||De(t).isValid())?Ke({to:this,from:t}).locale(this.locale()).humanize(!e):this.localeData().invalidDate()}function yi(t){return this.from(De(),t)}function bi(t,e){return this.isValid()&&(y(t)&&t.isValid()||De(t).isValid())?Ke({from:this,to:t}).locale(this.locale()).humanize(!e):this.localeData().invalidDate()}function _i(t){return this.to(De(),t)}function wi(t){var e;return void 0===t?this._locale._abbr:(e=ie(t),null!=e&&(this._locale=e),this)}function xi(){return this._locale}function ki(t){switch(t=z(t)){case"year":this.month(0);case"quarter":case"month":this.date(1);case"week":case"isoWeek":case"day":case"date":this.hours(0);case"hour":this.minutes(0);case"minute":this.seconds(0);case"second":this.milliseconds(0)}return"week"===t&&this.weekday(0),"isoWeek"===t&&this.isoWeekday(1),"quarter"===t&&this.month(3*Math.floor(this.month()/3)),this}function Si(t){return void 0===(t=z(t))||"millisecond"===t?this:("date"===t&&(t="day"),this.startOf(t).add(1,"isoWeek"===t?"week":t).subtract(1,"ms"))}function Di(){return this._d.valueOf()-6e4*(this._offset||0)}function Mi(){return Math.floor(this.valueOf()/1e3)}function Ci(){return new Date(this.valueOf())}function Oi(){var t=this;return[t.year(),t.month(),t.date(),t.hour(),t.minute(),t.second(),t.millisecond()]}function Ei(){var t=this;return{years:t.year(),months:t.month(),date:t.date(),hours:t.hours(),minutes:t.minutes(),seconds:t.seconds(),milliseconds:t.milliseconds()}}function Ti(){return this.isValid()?this.toISOString():null}function Pi(){return f(this)}function Ii(){return l({},p(this))}function Ni(){return p(this).overflow}function Ri(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}}function Ai(t,e){H(0,[t,t.length],0,e)}function zi(t){return ji.call(this,t,this.week(),this.weekday(),this.localeData()._week.dow,this.localeData()._week.doy)}function Li(t){return ji.call(this,t,this.isoWeek(),this.isoWeekday(),1,4)}function Fi(){return St(this.year(),1,4)}function Bi(){var t=this.localeData()._week;return St(this.year(),t.dow,t.doy)}function ji(t,e,i,o,n){var s;return null==t?kt(this,o,n).year:(s=St(t,o,n),e>s&&(e=s),Hi.call(this,t,e,i,o,n))}function Hi(t,e,i,o,n){var s=xt(t,e,i,o,n),r=_t(s.year,0,s.dayOfYear);return this.year(r.getUTCFullYear()),this.month(r.getUTCMonth()),this.date(r.getUTCDate()),this}function Wi(t){return null==t?Math.ceil((this.month()+1)/3):this.month(3*(t-1)+this.month()%3)}function Yi(t){var e=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==t?e:this.add(t-e,"d")}function Gi(t,e){e[mn]=_(1e3*("0."+t))}function Vi(){return this._isUTC?"UTC":""}function Ui(){return this._isUTC?"Coordinated Universal Time":""}function qi(t){return De(1e3*t)}function Xi(){return De.apply(null,arguments).parseZone()}function Zi(t){return t}function Ki(t,e,i,o){var n=ie(),s=u().set(o,e);return n[i](s,t)}function Ji(t,e,i){if(r(t)&&(e=t,t=void 0),t=t||"",null!=e)return Ki(t,e,i,"month");var o,n=[];for(o=0;o<12;o++)n[o]=Ki(t,o,i,"month");return n}function $i(t,e,i,o){"boolean"==typeof t?(r(e)&&(i=e,e=void 0),e=e||""):(e=t,i=e,t=!1,r(e)&&(i=e,e=void 0),e=e||"");var n=ie(),s=t?n._week.dow:0;if(null!=i)return Ki(e,(i+s)%7,o,"day");var a,h=[];for(a=0;a<7;a++)h[a]=Ki(e,(a+s)%7,o,"day");return h}function Qi(t,e){return Ji(t,e,"months")}function to(t,e){return Ji(t,e,"monthsShort")}function eo(t,e,i){return $i(t,e,i,"weekdays")}function io(t,e,i){return $i(t,e,i,"weekdaysShort")}function oo(t,e,i){return $i(t,e,i,"weekdaysMin")}function no(){var t=this._data;return this._milliseconds=ds(this._milliseconds),this._days=ds(this._days),this._months=ds(this._months),t.milliseconds=ds(t.milliseconds),t.seconds=ds(t.seconds),t.minutes=ds(t.minutes),t.hours=ds(t.hours),t.months=ds(t.months),t.years=ds(t.years),this}function so(t,e,i,o){var n=Ke(e,i);return t._milliseconds+=o*n._milliseconds,t._days+=o*n._days,t._months+=o*n._months,t._bubble()}function ro(t,e){return so(this,t,e,1)}function ao(t,e){return so(this,t,e,-1)}function ho(t){return t<0?Math.floor(t):Math.ceil(t)}function lo(){var t,e,i,o,n,s=this._milliseconds,r=this._days,a=this._months,h=this._data;return s>=0&&r>=0&&a>=0||s<=0&&r<=0&&a<=0||(s+=864e5*ho(co(a)+r),r=0,a=0),h.milliseconds=s%1e3,t=b(s/1e3),h.seconds=t%60,e=b(t/60),h.minutes=e%60,i=b(e/60),h.hours=i%24,r+=b(i/24),n=b(uo(r)),a+=n, r-=ho(co(n)),o=b(a/12),a%=12,h.days=r,h.months=a,h.years=o,this}function uo(t){return 4800*t/146097}function co(t){return 146097*t/4800}function po(t){if(!this.isValid())return NaN;var e,i,o=this._milliseconds;if("month"===(t=z(t))||"year"===t)return e=this._days+o/864e5,i=this._months+uo(e),"month"===t?i:i/12;switch(e=this._days+Math.round(co(this._months)),t){case"week":return e/7+o/6048e5;case"day":return e+o/864e5;case"hour":return 24*e+o/36e5;case"minute":return 1440*e+o/6e4;case"second":return 86400*e+o/1e3;case"millisecond":return Math.floor(864e5*e)+o;default:throw new Error("Unknown unit "+t)}}function fo(){return this.isValid()?this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*_(this._months/12):NaN}function mo(t){return function(){return this.as(t)}}function vo(){return Ke(this)}function go(t){return t=z(t),this.isValid()?this[t+"s"]():NaN}function yo(t){return function(){return this.isValid()?this._data[t]:NaN}}function bo(){return b(this.days()/7)}function _o(t,e,i,o,n){return n.relativeTime(e||1,!!i,t,o)}function wo(t,e,i){var o=Ke(t).abs(),n=Ds(o.as("s")),s=Ds(o.as("m")),r=Ds(o.as("h")),a=Ds(o.as("d")),h=Ds(o.as("M")),d=Ds(o.as("y")),l=n<=Ms.ss&&["s",n]||n0,l[4]=i,_o.apply(null,l)}function xo(t){return void 0===t?Ds:"function"==typeof t&&(Ds=t,!0)}function ko(t,e){return void 0!==Ms[t]&&(void 0===e?Ms[t]:(Ms[t]=e,"s"===t&&(Ms.ss=e-1),!0))}function So(t){if(!this.isValid())return this.localeData().invalidDate();var e=this.localeData(),i=wo(this,!t,e);return t&&(i=e.pastFuture(+this,i)),e.postformat(i)}function Do(t){return(t>0)-(t<0)||+t}function Mo(){if(!this.isValid())return this.localeData().invalidDate();var t,e,i,o=Cs(this._milliseconds)/1e3,n=Cs(this._days),s=Cs(this._months);t=b(o/60),e=b(t/60),o%=60,t%=60,i=b(s/12),s%=12;var r=i,a=s,h=n,d=e,l=t,u=o?o.toFixed(3).replace(/\.?0+$/,""):"",c=this.asSeconds();if(!c)return"P0D";var p=c<0?"-":"",f=Do(this._months)!==Do(c)?"-":"",m=Do(this._days)!==Do(c)?"-":"",v=Do(this._milliseconds)!==Do(c)?"-":"";return p+"P"+(r?f+r+"Y":"")+(a?f+a+"M":"")+(h?m+h+"D":"")+(d||l||u?"T":"")+(d?v+d+"H":"")+(l?v+l+"M":"")+(u?v+u+"S":"")}var Co,Oo;Oo=Array.prototype.some?Array.prototype.some:function(t){for(var e=Object(this),i=e.length>>>0,o=0;o68?1900:2e3)};var yn,bn=it("FullYear",!0);yn=Array.prototype.indexOf?Array.prototype.indexOf:function(t){var e;for(e=0;ethis?this:t:m()}),Xn=function(){return Date.now?Date.now():+new Date},Zn=["year","quarter","month","week","day","hour","minute","second","millisecond"];Ae("Z",":"),Ae("ZZ",""),U("Z",nn),U("ZZ",nn),K(["Z","ZZ"],function(t,e,i){i._useUTC=!0,i._tzm=ze(nn,t)});var Kn=/([\+\-]|\d\d)/gi;e.updateOffset=function(){};var Jn=/^(\-|\+)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/,$n=/^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;Ke.fn=Ie.prototype,Ke.invalid=Pe;var Qn=ti(1,"add"),ts=ti(-1,"subtract");e.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",e.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var es=k("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(t){return void 0===t?this.localeData():this.locale(t)});H(0,["gg",2],0,function(){return this.weekYear()%100}),H(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Ai("gggg","weekYear"),Ai("ggggg","weekYear"),Ai("GGGG","isoWeekYear"),Ai("GGGGG","isoWeekYear"),A("weekYear","gg"),A("isoWeekYear","GG"),F("weekYear",1),F("isoWeekYear",1),U("G",en),U("g",en),U("GG",Xo,Go),U("gg",Xo,Go),U("GGGG",$o,Uo),U("gggg",$o,Uo),U("GGGGG",Qo,qo),U("ggggg",Qo,qo),J(["gggg","ggggg","GGGG","GGGGG"],function(t,e,i,o){e[o.substr(0,2)]=_(t)}),J(["gg","GG"],function(t,i,o,n){i[n]=e.parseTwoDigitYear(t)}),H("Q",0,"Qo","quarter"),A("quarter","Q"),F("quarter",7),U("Q",Yo),K("Q",function(t,e){e[ln]=3*(_(t)-1)}),H("D",["DD",2],"Do","date"),A("date","D"),F("date",9),U("D",Xo),U("DD",Xo,Go),U("Do",function(t,e){return t?e._dayOfMonthOrdinalParse||e._ordinalParse:e._dayOfMonthOrdinalParseLenient}),K(["D","DD"],un),K("Do",function(t,e){e[un]=_(t.match(Xo)[0],10)});var is=it("Date",!0);H("DDD",["DDDD",3],"DDDo","dayOfYear"),A("dayOfYear","DDD"),F("dayOfYear",4),U("DDD",Jo),U("DDDD",Vo),K(["DDD","DDDD"],function(t,e,i){i._dayOfYear=_(t)}),H("m",["mm",2],0,"minute"),A("minute","m"),F("minute",14),U("m",Xo),U("mm",Xo,Go),K(["m","mm"],pn);var os=it("Minutes",!1);H("s",["ss",2],0,"second"),A("second","s"),F("second",15),U("s",Xo),U("ss",Xo,Go),K(["s","ss"],fn);var ns=it("Seconds",!1);H("S",0,0,function(){return~~(this.millisecond()/100)}),H(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),H(0,["SSS",3],0,"millisecond"),H(0,["SSSS",4],0,function(){return 10*this.millisecond()}),H(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),H(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),H(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),H(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),H(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),A("millisecond","ms"),F("millisecond",16),U("S",Jo,Yo),U("SS",Jo,Go),U("SSS",Jo,Vo);var ss;for(ss="SSSS";ss.length<=9;ss+="S")U(ss,tn);for(ss="S";ss.length<=9;ss+="S")K(ss,Gi);var rs=it("Milliseconds",!1);H("z",0,0,"zoneAbbr"),H("zz",0,0,"zoneName");var as=g.prototype;as.add=Qn,as.calendar=oi,as.clone=ni,as.diff=ui,as.endOf=Si,as.format=vi,as.from=gi,as.fromNow=yi,as.to=bi,as.toNow=_i,as.get=st,as.invalidAt=Ni,as.isAfter=si,as.isBefore=ri,as.isBetween=ai,as.isSame=hi,as.isSameOrAfter=di,as.isSameOrBefore=li,as.isValid=Pi,as.lang=es,as.locale=wi,as.localeData=xi,as.max=qn,as.min=Un,as.parsingFlags=Ii,as.set=rt,as.startOf=ki,as.subtract=ts,as.toArray=Oi,as.toObject=Ei,as.toDate=Ci,as.toISOString=fi,as.inspect=mi,as.toJSON=Ti,as.toString=pi,as.unix=Mi,as.valueOf=Di,as.creationData=Ri,as.year=bn,as.isLeapYear=et,as.weekYear=zi,as.isoWeekYear=Li,as.quarter=as.quarters=Wi,as.month=ft,as.daysInMonth=mt,as.week=as.weeks=Ot,as.isoWeek=as.isoWeeks=Et,as.weeksInYear=Bi,as.isoWeeksInYear=Fi,as.date=is,as.day=as.days=Lt,as.weekday=Ft,as.isoWeekday=Bt,as.dayOfYear=Yi,as.hour=as.hours=Rn,as.minute=as.minutes=os,as.second=as.seconds=ns,as.millisecond=as.milliseconds=rs,as.utcOffset=Be,as.utc=He,as.local=We,as.parseZone=Ye,as.hasAlignedHourOffset=Ge,as.isDST=Ve,as.isLocal=qe,as.isUtcOffset=Xe,as.isUtc=Ze,as.isUTC=Ze,as.zoneAbbr=Vi,as.zoneName=Ui,as.dates=k("dates accessor is deprecated. Use date instead.",is),as.months=k("months accessor is deprecated. Use month instead",ft),as.years=k("years accessor is deprecated. Use year instead",bn),as.zone=k("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",je),as.isDSTShifted=k("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",Ue);var hs=O.prototype;hs.calendar=E,hs.longDateFormat=T,hs.invalidDate=P,hs.ordinal=I,hs.preparse=Zi,hs.postformat=Zi,hs.relativeTime=N,hs.pastFuture=R,hs.set=M,hs.months=dt,hs.monthsShort=lt,hs.monthsParse=ct,hs.monthsRegex=gt,hs.monthsShortRegex=vt,hs.week=Dt,hs.firstDayOfYear=Ct,hs.firstDayOfWeek=Mt,hs.weekdays=It,hs.weekdaysMin=Rt,hs.weekdaysShort=Nt,hs.weekdaysParse=zt,hs.weekdaysRegex=jt,hs.weekdaysShortRegex=Ht,hs.weekdaysMinRegex=Wt,hs.isPM=Xt,hs.meridiem=Zt,Qt("en",{dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(t){var e=t%10;return t+(1===_(t%100/10)?"th":1===e?"st":2===e?"nd":3===e?"rd":"th")}}),e.lang=k("moment.lang is deprecated. Use moment.locale instead.",Qt),e.langData=k("moment.langData is deprecated. Use moment.localeData instead.",ie);var ds=Math.abs,ls=mo("ms"),us=mo("s"),cs=mo("m"),ps=mo("h"),fs=mo("d"),ms=mo("w"),vs=mo("M"),gs=mo("y"),ys=yo("milliseconds"),bs=yo("seconds"),_s=yo("minutes"),ws=yo("hours"),xs=yo("days"),ks=yo("months"),Ss=yo("years"),Ds=Math.round,Ms={ss:44,s:45,m:45,h:22,d:26,M:11},Cs=Math.abs,Os=Ie.prototype;return Os.isValid=Te,Os.abs=no,Os.add=ro,Os.subtract=ao,Os.as=po,Os.asMilliseconds=ls,Os.asSeconds=us,Os.asMinutes=cs,Os.asHours=ps,Os.asDays=fs,Os.asWeeks=ms,Os.asMonths=vs,Os.asYears=gs,Os.valueOf=fo,Os._bubble=lo,Os.clone=vo,Os.get=go,Os.milliseconds=ys,Os.seconds=bs,Os.minutes=_s,Os.hours=ws,Os.days=xs,Os.weeks=bo,Os.months=ks,Os.years=Ss,Os.humanize=So,Os.toISOString=Mo,Os.toString=Mo,Os.toJSON=Mo,Os.locale=wi,Os.localeData=xi,Os.toIsoString=k("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",Mo),Os.lang=es,H("X",0,0,"unix"),H("x",0,0,"valueOf"),U("x",en),U("X",sn),K("X",function(t,e,i){i._d=new Date(1e3*parseFloat(t,10))}),K("x",function(t,e,i){i._d=new Date(_(t))}),e.version="2.19.1",function(t){Co=t}(De),e.fn=as,e.min=Ce,e.max=Oe,e.now=Xn,e.utc=u,e.unix=qi,e.months=Qi,e.isDate=a,e.locale=Qt,e.invalid=m,e.duration=Ke,e.isMoment=y,e.weekdays=eo,e.parseZone=Xi,e.localeData=ie,e.isDuration=Ne,e.monthsShort=to,e.weekdaysMin=oo,e.defineLocale=te,e.updateLocale=ee,e.locales=oe,e.weekdaysShort=io,e.normalizeUnits=z,e.relativeTimeRounding=xo,e.relativeTimeThreshold=ko,e.calendarFormat=ii,e.prototype=as,e})}).call(e,i(155)(t))},function(t,e){t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children||(t.children=[]),Object.defineProperty(t,"loaded",{enumerable:!0,get:function(){return t.l}}),Object.defineProperty(t,"id",{enumerable:!0,get:function(){return t.i}}),t.webpackPolyfill=1),t}},function(t,e){function i(t){throw new Error("Cannot find module '"+t+"'.")}i.keys=function(){return[]},i.resolve=i,t.exports=i,i.id=156},function(t,e,i){(function(e){function i(t,e,i){var o=e&&i||0,n=0;for(e=e||[],t.toLowerCase().replace(/[0-9a-f]{2}/g,function(t){n<16&&(e[o+n++]=u[t])});n<16;)e[o+n++]=0;return e}function o(t,e){var i=e||0,o=l;return o[t[i++]]+o[t[i++]]+o[t[i++]]+o[t[i++]]+"-"+o[t[i++]]+o[t[i++]]+"-"+o[t[i++]]+o[t[i++]]+"-"+o[t[i++]]+o[t[i++]]+"-"+o[t[i++]]+o[t[i++]]+o[t[i++]]+o[t[i++]]+o[t[i++]]+o[t[i++]]}function n(t,e,i){var n=e&&i||0,s=e||[];t=t||{};var r=void 0!==t.clockseq?t.clockseq:m,a=void 0!==t.msecs?t.msecs:(new Date).getTime(),h=void 0!==t.nsecs?t.nsecs:g+1,d=a-v+(h-g)/1e4;if(d<0&&void 0===t.clockseq&&(r=r+1&16383),(d<0||a>v)&&void 0===t.nsecs&&(h=0),h>=1e4)throw new Error("uuid.v1(): Can't create more than 10M uuids/sec");v=a,g=h,m=r,a+=122192928e5;var l=(1e4*(268435455&a)+h)%4294967296;s[n++]=l>>>24&255,s[n++]=l>>>16&255,s[n++]=l>>>8&255,s[n++]=255&l;var u=a/4294967296*1e4&268435455;s[n++]=u>>>8&255,s[n++]=255&u,s[n++]=u>>>24&15|16,s[n++]=u>>>16&255,s[n++]=r>>>8|128,s[n++]=255&r;for(var c=t.node||f,p=0;p<6;p++)s[n+p]=c[p];return e||o(s)}function s(t,e,i){var n=e&&i||0;"string"==typeof t&&(e="binary"==t?new Array(16):null,t=null),t=t||{};var s=t.random||(t.rng||r)();if(s[6]=15&s[6]|64,s[8]=63&s[8]|128,e)for(var a=0;a<16;a++)e[n+a]=s[a];return e||o(s)}var r,a="undefined"!=typeof window?window:void 0!==e?e:null;if(a&&a.crypto&&crypto.getRandomValues){var h=new Uint8Array(16);r=function(){return crypto.getRandomValues(h),h}}if(!r){var d=new Array(16);r=function(){for(var t,e=0;e<16;e++)0==(3&e)&&(t=4294967296*Math.random()),d[e]=t>>>((3&e)<<3)&255;return d}}for(var l=[],u={},c=0;c<256;c++)l[c]=(c+256).toString(16).substr(1),u[l[c]]=c;var p=r(),f=[1|p[0],p[1],p[2],p[3],p[4],p[5]],m=16383&(p[6]<<8|p[7]),v=0,g=0,y=s;y.v1=n,y.v4=s,y.parse=i,y.unparse=o,t.exports=y}).call(e,i(158))},function(t,e){var i;i=function(){return this}();try{i=i||Function("return this")()||(0,eval)("this")}catch(t){"object"==typeof window&&(i=window)}t.exports=i},function(t,e,i){e.util=i(2),e.DOMutil=i(14),e.DataSet=i(11),e.DataView=i(12),e.Queue=i(43),e.Graph3d=i(161),e.graph3d={Camera:i(95),Filter:i(96),Point2d:i(91),Point3d:i(34),Slider:i(92),StepNumber:i(93)},e.moment=i(9),e.Hammer=i(10),e.keycharm=i(35)},function(t,e,i){var o=i(7),n=o.JSON||(o.JSON={stringify:JSON.stringify});t.exports=function(t){return n.stringify.apply(n,arguments)}},function(t,e,i){function o(t,e,i){if(!(this instanceof o))throw new SyntaxError("Constructor must be called with the new operator");this.containerElement=t,this.dataGroup=new _,this.dataPoints=null,this.create(),f.setDefaults(o.DEFAULTS,this),this.colX=void 0,this.colY=void 0,this.colZ=void 0,this.colValue=void 0,this.setOptions(i),this.setData(e)}function n(t){return"clientX"in t?t.clientX:t.targetTouches[0]&&t.targetTouches[0].clientX||0}function s(t){return"clientY"in t?t.clientY:t.targetTouches[0]&&t.targetTouches[0].clientY||0}var r=i(90),a=function(t){return t&&t.__esModule?t:{default:t}}(r),h=i(44),d=i(2),l=i(34),u=i(91),c=i(92),p=i(93),f=i(94),m=i(15).default,v=i(15),g=v.printStyle,y=i(172),b=y.allOptions,_=i(173);o.STYLE=f.STYLE;o.DEFAULTS={width:"400px",height:"400px",filterLabel:"time",legendLabel:"value",xLabel:"x",yLabel:"y",zLabel:"z",xValueLabel:function(t){return t},yValueLabel:function(t){return t},zValueLabel:function(t){return t},showXAxis:!0,showYAxis:!0,showZAxis:!0,showGrid:!0,showPerspective:!0,showShadow:!1,keepAspectRatio:!0,verticalRatio:.5,dotSizeRatio:.02,dotSizeMinFraction:.5,dotSizeMaxFraction:2.5,showAnimationControls:void 0,animationInterval:1e3,animationPreload:!1,animationAutoStart:void 0,axisColor:"#4D4D4D",gridColor:"#D3D3D3",xCenter:"55%",yCenter:"50%",style:o.STYLE.DOT,tooltip:!1,tooltipStyle:{content:{padding:"10px",border:"1px solid #4d4d4d",color:"#1a1a1a",background:"rgba(255,255,255,0.7)",borderRadius:"2px",boxShadow:"5px 5px 10px rgba(128,128,128,0.5)"},line:{height:"40px",width:"0",borderLeft:"1px solid #4d4d4d"},dot:{height:"0",width:"0",border:"5px solid #4d4d4d",borderRadius:"5px"}},dataColor:{fill:"#7DC1FF",stroke:"#3267D2",strokeWidth:1},cameraPosition:{horizontal:1,vertical:.5,distance:1.7},showLegend:void 0,backgroundColor:void 0,xBarWidth:void 0,yBarWidth:void 0,valueMin:void 0,valueMax:void 0,xMin:void 0,xMax:void 0,xStep:void 0,yMin:void 0,yMax:void 0,yStep:void 0,zMin:void 0,zMax:void 0,zStep:void 0},h(o.prototype),o.prototype._setScale=function(){this.scale=new l(1/this.xRange.range(),1/this.yRange.range(),1/this.zRange.range()),this.keepAspectRatio&&(this.scale.x0&&(r[n-1].pointNext=r[n]);return r},o.prototype.create=function(){for(;this.containerElement.hasChildNodes();)this.containerElement.removeChild(this.containerElement.firstChild);this.frame=document.createElement("div"),this.frame.style.position="relative",this.frame.style.overflow="hidden",this.frame.canvas=document.createElement("canvas"),this.frame.canvas.style.position="relative",this.frame.appendChild(this.frame.canvas);var t=document.createElement("DIV");t.style.color="red",t.style.fontWeight="bold",t.style.padding="10px",t.innerHTML="Error: your browser does not support HTML canvas",this.frame.canvas.appendChild(t),this.frame.filter=document.createElement("div"),this.frame.filter.style.position="absolute",this.frame.filter.style.bottom="0px",this.frame.filter.style.left="0px",this.frame.filter.style.width="100%",this.frame.appendChild(this.frame.filter);var e=this,i=function(t){e._onMouseDown(t)},o=function(t){e._onTouchStart(t)},n=function(t){e._onWheel(t)},s=function(t){e._onTooltip(t)},r=function(t){e._onClick(t)};d.addEventListener(this.frame.canvas,"mousedown",i),d.addEventListener(this.frame.canvas,"touchstart",o),d.addEventListener(this.frame.canvas,"mousewheel",n),d.addEventListener(this.frame.canvas,"mousemove",s),d.addEventListener(this.frame.canvas,"click",r),this.containerElement.appendChild(this.frame)},o.prototype._setSize=function(t,e){this.frame.style.width=t,this.frame.style.height=e,this._resizeCanvas()},o.prototype._resizeCanvas=function(){this.frame.canvas.style.width="100%",this.frame.canvas.style.height="100%",this.frame.canvas.width=this.frame.canvas.clientWidth,this.frame.canvas.height=this.frame.canvas.clientHeight,this.frame.filter.style.width=this.frame.canvas.clientWidth-20+"px"},o.prototype.animationStart=function(){if(this.animationAutoStart&&this.dataGroup.dataFilter){if(!this.frame.filter||!this.frame.filter.slider)throw new Error("No animation available");this.frame.filter.slider.play()}},o.prototype.animationStop=function(){this.frame.filter&&this.frame.filter.slider&&this.frame.filter.slider.stop()},o.prototype._resizeCenter=function(){"%"===this.xCenter.charAt(this.xCenter.length-1)?this.currentXCenter=parseFloat(this.xCenter)/100*this.frame.canvas.clientWidth:this.currentXCenter=parseFloat(this.xCenter),"%"===this.yCenter.charAt(this.yCenter.length-1)?this.currentYCenter=parseFloat(this.yCenter)/100*(this.frame.canvas.clientHeight-this.frame.filter.clientHeight):this.currentYCenter=parseFloat(this.yCenter)},o.prototype.getCameraPosition=function(){var t=this.camera.getArmRotation();return t.distance=this.camera.getArmLength(),t},o.prototype._readData=function(t){this.dataPoints=this.dataGroup.initializeData(this,t,this.style),this._initializeRanges(),this._redrawFilter()},o.prototype.setData=function(t){void 0!==t&&null!==t&&(this._readData(t),this.redraw(),this.animationStart())},o.prototype.setOptions=function(t){if(void 0!==t){!0===m.validate(t,b)&&console.log("%cErrors have been found in the supplied options object.",g),this.animationStop(),f.setOptions(t,this),this.setPointDrawingMethod(),this._setSize(this.width,this.height),this.setData(this.dataGroup.getDataTable()),this.animationStart()}},o.prototype.setPointDrawingMethod=function(){var t=void 0;switch(this.style){case o.STYLE.BAR:t=o.prototype._redrawBarGraphPoint;break;case o.STYLE.BARCOLOR:t=o.prototype._redrawBarColorGraphPoint;break;case o.STYLE.BARSIZE:t=o.prototype._redrawBarSizeGraphPoint;break;case o.STYLE.DOT:t=o.prototype._redrawDotGraphPoint;break;case o.STYLE.DOTLINE:t=o.prototype._redrawDotLineGraphPoint;break;case o.STYLE.DOTCOLOR:t=o.prototype._redrawDotColorGraphPoint;break;case o.STYLE.DOTSIZE:t=o.prototype._redrawDotSizeGraphPoint;break;case o.STYLE.SURFACE:t=o.prototype._redrawSurfaceGraphPoint;break;case o.STYLE.GRID:t=o.prototype._redrawGridGraphPoint;break;case o.STYLE.LINE:t=o.prototype._redrawLineGraphPoint;break;default:throw new Error("Can not determine point drawing method for graph style '"+this.style+"'")}this._pointDrawingMethod=t},o.prototype.redraw=function(){if(void 0===this.dataPoints)throw new Error("Graph data not initialized");this._resizeCanvas(),this._resizeCenter(),this._redrawSlider(),this._redrawClear(),this._redrawAxis(),this._redrawDataGraph(),this._redrawInfo(),this._redrawLegend()},o.prototype._getContext=function(){var t=this.frame.canvas,e=t.getContext("2d");return e.lineJoin="round",e.lineCap="round",e},o.prototype._redrawClear=function(){var t=this.frame.canvas;t.getContext("2d").clearRect(0,0,t.width,t.height)},o.prototype._dotSize=function(){return this.frame.clientWidth*this.dotSizeRatio},o.prototype._getLegendWidth=function(){var t;if(this.style===o.STYLE.DOTSIZE){t=this._dotSize()*this.dotSizeMaxFraction}else t=this.style===o.STYLE.BARSIZE?this.xBarWidth:20;return t},o.prototype._redrawLegend=function(){if(!0===this.showLegend&&this.style!==o.STYLE.LINE&&this.style!==o.STYLE.BARSIZE){var t=this.style===o.STYLE.BARSIZE||this.style===o.STYLE.DOTSIZE,e=this.style===o.STYLE.DOTSIZE||this.style===o.STYLE.DOTCOLOR||this.style===o.STYLE.BARCOLOR,i=Math.max(.25*this.frame.clientHeight,100),n=this.margin,s=this._getLegendWidth(),r=this.frame.clientWidth-this.margin,a=r-s,h=n+i,d=this._getContext();if(d.lineWidth=1,d.font="14px arial",!1===t){var l,c=i;for(l=0;l0?(t.textAlign="center",t.textBaseline="top",s.y+=n):Math.sin(2*o)<0?(t.textAlign="right",t.textBaseline="middle"):(t.textAlign="left",t.textBaseline="middle"),t.fillStyle=this.axisColor,t.fillText(i,s.x,s.y)},o.prototype.drawAxisLabelY=function(t,e,i,o,n){void 0===n&&(n=0);var s=this._convert3Dto2D(e);Math.cos(2*o)<0?(t.textAlign="center",t.textBaseline="top",s.y+=n):Math.sin(2*o)>0?(t.textAlign="right",t.textBaseline="middle"):(t.textAlign="left",t.textBaseline="middle"),t.fillStyle=this.axisColor,t.fillText(i,s.x,s.y)},o.prototype.drawAxisLabelZ=function(t,e,i,o){void 0===o&&(o=0);var n=this._convert3Dto2D(e);t.textAlign="right",t.textBaseline="middle",t.fillStyle=this.axisColor,t.fillText(i,n.x-o,n.y)},o.prototype._line3d=function(t,e,i,o){var n=this._convert3Dto2D(e),s=this._convert3Dto2D(i);this._line(t,n,s,o)},o.prototype._redrawAxis=function(){var t,e,i,o,n,s,r,a,h,d,c,f=this._getContext();f.font=24/this.camera.getArmLength()+"px arial";var m,v=.025/this.scale.x,g=.025/this.scale.y,y=5/this.camera.getArmLength(),b=this.camera.getArmRotation().horizontal,_=new u(Math.cos(b),Math.sin(b)),w=this.xRange,x=this.yRange,k=this.zRange;for(f.lineWidth=1,o=void 0===this.defaultXStep,i=new p(w.min,w.max,this.xStep,o),i.start(!0);!i.end();){var S=i.getCurrent();if(this.showGrid?(t=new l(S,x.min,k.min),e=new l(S,x.max,k.min),this._line3d(f,t,e,this.gridColor)):this.showXAxis&&(t=new l(S,x.min,k.min),e=new l(S,x.min+v,k.min),this._line3d(f,t,e,this.axisColor),t=new l(S,x.max,k.min),e=new l(S,x.max-v,k.min),this._line3d(f,t,e,this.axisColor)),this.showXAxis){r=_.x>0?x.min:x.max,m=new l(S,r,k.min);var D=" "+this.xValueLabel(S)+" ";this.drawAxisLabelX(f,m,D,b,y)}i.next()}for(f.lineWidth=1,o=void 0===this.defaultYStep,i=new p(x.min,x.max,this.yStep,o),i.start(!0);!i.end();){var M=i.getCurrent();if(this.showGrid?(t=new l(w.min,M,k.min),e=new l(w.max,M,k.min),this._line3d(f,t,e,this.gridColor)):this.showYAxis&&(t=new l(w.min,M,k.min),e=new l(w.min+g,M,k.min),this._line3d(f,t,e,this.axisColor),t=new l(w.max,M,k.min),e=new l(w.max-g,M,k.min),this._line3d(f,t,e,this.axisColor)),this.showYAxis){s=_.y>0?w.min:w.max,m=new l(s,M,k.min);var C=" "+this.yValueLabel(M)+" ";this.drawAxisLabelY(f,m,C,b,y)}i.next()}if(this.showZAxis){for(f.lineWidth=1,o=void 0===this.defaultZStep,i=new p(k.min,k.max,this.zStep,o),i.start(!0),s=_.x>0?w.min:w.max,r=_.y<0?x.min:x.max;!i.end();){var O=i.getCurrent(),E=new l(s,r,O),T=this._convert3Dto2D(E);e=new u(T.x-y,T.y),this._line(f,T,e,this.axisColor);var P=this.zValueLabel(O)+" ";this.drawAxisLabelZ(f,E,P,5),i.next()}f.lineWidth=1,t=new l(s,r,k.min),e=new l(s,r,k.max),this._line3d(f,t,e,this.axisColor)}if(this.showXAxis){var I,N;f.lineWidth=1,I=new l(w.min,x.min,k.min),N=new l(w.max,x.min,k.min),this._line3d(f,I,N,this.axisColor),I=new l(w.min,x.max,k.min),N=new l(w.max,x.max,k.min),this._line3d(f,I,N,this.axisColor)}this.showYAxis&&(f.lineWidth=1,t=new l(w.min,x.min,k.min),e=new l(w.min,x.max,k.min),this._line3d(f,t,e,this.axisColor),t=new l(w.max,x.min,k.min),e=new l(w.max,x.max,k.min),this._line3d(f,t,e,this.axisColor));var R=this.xLabel;R.length>0&&this.showXAxis&&(c=.1/this.scale.y,s=(w.max+3*w.min)/4,r=_.x>0?x.min-c:x.max+c,n=new l(s,r,k.min),this.drawAxisLabelX(f,n,R,b));var A=this.yLabel;A.length>0&&this.showYAxis&&(d=.1/this.scale.x,s=_.y>0?w.min-d:w.max+d,r=(x.max+3*x.min)/4,n=new l(s,r,k.min),this.drawAxisLabelY(f,n,A,b));var z=this.zLabel;z.length>0&&this.showZAxis&&(h=30,s=_.x>0?w.min:w.max,r=_.y<0?x.min:x.max,a=(k.max+3*k.min)/4,n=new l(s,r,a),this.drawAxisLabelZ(f,n,z,h))},o.prototype._hsv2rgb=function(t,e,i){var o,n,s,r,a,h;switch(r=i*e,a=Math.floor(t/60),h=r*(1-Math.abs(t/60%2-1)),a){case 0:o=r,n=h,s=0;break;case 1:o=h,n=r,s=0;break;case 2:o=0,n=r,s=h;break;case 3:o=0,n=h,s=r;break;case 4:o=h,n=0,s=r;break;case 5:o=r,n=0,s=h;break;default:o=0,n=0,s=0}return"RGB("+parseInt(255*o)+","+parseInt(255*n)+","+parseInt(255*s)+")"},o.prototype._getStrokeWidth=function(t){return void 0!==t?this.showPerspective?1/-t.trans.z*this.dataColor.strokeWidth:-this.eye.z/this.camera.getArmLength()*this.dataColor.strokeWidth:this.dataColor.strokeWidth},o.prototype._redrawBar=function(t,e,i,o,n,s){var r,a=this,h=e.point,d=this.zRange.min,u=[{point:new l(h.x-i,h.y-o,h.z)},{point:new l(h.x+i,h.y-o,h.z)},{point:new l(h.x+i,h.y+o,h.z)},{point:new l(h.x-i,h.y+o,h.z)}],c=[{point:new l(h.x-i,h.y-o,d)},{point:new l(h.x+i,h.y-o,d)},{point:new l(h.x+i,h.y+o,d)},{point:new l(h.x-i,h.y+o,d)}];u.forEach(function(t){t.screen=a._convert3Dto2D(t.point)}),c.forEach(function(t){t.screen=a._convert3Dto2D(t.point)});var p=[{corners:u,center:l.avg(c[0].point,c[2].point)},{corners:[u[0],u[1],c[1],c[0]],center:l.avg(c[1].point,c[0].point)},{corners:[u[1],u[2],c[2],c[1]],center:l.avg(c[2].point,c[1].point)},{corners:[u[2],u[3],c[3],c[2]],center:l.avg(c[3].point,c[2].point)},{corners:[u[3],u[0],c[0],c[3]],center:l.avg(c[0].point,c[3].point)}];e.surfaces=p;for(var f=0;f0}if(a){var p,f=(e.point.z+i.point.z+o.point.z+n.point.z)/4,m=240*(1-(f-this.zRange.min)*this.scale.z/this.verticalRatio);this.showShadow?(p=Math.min(1+u.x/c/2,1),s=this._hsv2rgb(m,1,p),r=s):(p=1,s=this._hsv2rgb(m,1,p),r=this.axisColor)}else s="gray",r=this.axisColor;t.lineWidth=this._getStrokeWidth(e);var v=[e,i,n,o];this._polygon(t,v,s,r)}},o.prototype._drawGridLine=function(t,e,i){if(void 0!==e&&void 0!==i){var o=(e.point.z+i.point.z)/2,n=240*(1-(o-this.zRange.min)*this.scale.z/this.verticalRatio);t.lineWidth=2*this._getStrokeWidth(e),t.strokeStyle=this._hsv2rgb(n,1,1),this._line(t,e.screen,i.screen)}},o.prototype._redrawGridGraphPoint=function(t,e){this._drawGridLine(t,e,e.pointRight),this._drawGridLine(t,e,e.pointTop)},o.prototype._redrawLineGraphPoint=function(t,e){void 0!==e.pointNext&&(t.lineWidth=this._getStrokeWidth(e),t.strokeStyle=this.dataColor.stroke,this._line(t,e.screen,e.pointNext.screen))},o.prototype._redrawDataGraph=function(){var t,e=this._getContext();if(!(void 0===this.dataPoints||this.dataPoints.length<=0))for(this._calcTranslations(this.dataPoints),t=0;t0?1:t<0?-1:0}var o=e[0],n=e[1],s=e[2],r=i((n.x-o.x)*(t.y-o.y)-(n.y-o.y)*(t.x-o.x)),a=i((s.x-n.x)*(t.y-n.y)-(s.y-n.y)*(t.x-n.x)),h=i((o.x-s.x)*(t.y-s.y)-(o.y-s.y)*(t.x-s.x));return!(0!=r&&0!=a&&r!=a||0!=a&&0!=h&&a!=h||0!=r&&0!=h&&r!=h)},o.prototype._dataPointFromXY=function(t,e){var i,n=null,s=null,r=null,a=new u(t,e);if(this.style===o.STYLE.BAR||this.style===o.STYLE.BARCOLOR||this.style===o.STYLE.BARSIZE)for(i=this.dataPoints.length-1;i>=0;i--){n=this.dataPoints[i];var h=n.surfaces;if(h)for(var d=h.length-1;d>=0;d--){var l=h[d],c=l.corners,p=[c[0].screen,c[1].screen,c[2].screen],f=[c[2].screen,c[3].screen,c[0].screen];if(this._insideTriangle(a,p)||this._insideTriangle(a,f))return n}}else for(i=0;i"+this.xLabel+":"+t.point.x+""+this.yLabel+":"+t.point.y+""+this.zLabel+":"+t.point.z+"",e.style.left="0",e.style.top="0",this.frame.appendChild(e),this.frame.appendChild(i),this.frame.appendChild(o);var n=e.offsetWidth,s=e.offsetHeight,r=i.offsetHeight,h=o.offsetWidth,d=o.offsetHeight,l=t.screen.x-n/2;l=Math.min(Math.max(l,10),this.frame.clientWidth-10-n),i.style.left=t.screen.x+"px",i.style.top=t.screen.y-r+"px",e.style.left=l+"px",e.style.top=t.screen.y-r-s+"px",o.style.left=t.screen.x-h/2+"px",o.style.top=t.screen.y-d/2+"px"},o.prototype._hideTooltip=function(){if(this.tooltip){this.tooltip.dataPoint=null;for(var t in this.tooltip.dom)if(this.tooltip.dom.hasOwnProperty(t)){var e=this.tooltip.dom[t];e&&e.parentNode&&e.parentNode.removeChild(e)}}},o.prototype.setCameraPosition=function(t){f.setCameraPosition(t,this),this.redraw()},o.prototype.setSize=function(t,e){this._setSize(t,e),this.redraw()},t.exports=o},function(t,e,i){i(163),t.exports=i(7).Object.assign},function(t,e,i){var o=i(17);o(o.S+o.F,"Object",{assign:i(164)})},function(t,e,i){var o=i(33),n=i(63),s=i(42),r=i(41),a=i(78),h=Object.assign;t.exports=!h||i(28)(function(){var t={},e={},i=Symbol(),o="abcdefghijklmnopqrst";return t[i]=7,o.split("").forEach(function(t){e[t]=t}),7!=h({},t)[i]||Object.keys(h({},e)).join("")!=o})?function(t,e){for(var i=r(t),h=arguments.length,d=1,l=n.f,u=s.f;h>d;)for(var c,p=a(arguments[d++]),f=l?o(p).concat(l(p)):o(p),m=f.length,v=0;m>v;)u.call(p,c=f[v++])&&(i[c]=p[c]);return i}:h},function(t,e,i){t.exports={default:i(166),__esModule:!0}},function(t,e,i){i(167),t.exports=i(7).Math.sign},function(t,e,i){var o=i(17);o(o.S,"Math",{sign:i(168)})},function(t,e){t.exports=Math.sign||function(t){return 0==(t=+t)||t!=t?t:t<0?-1:1}},function(t,e,i){t.exports={default:i(170),__esModule:!0}},function(t,e,i){i(171);var o=i(7).Object;t.exports=function(t,e,i){return o.defineProperty(t,e,i)}},function(t,e,i){var o=i(17);o(o.S+o.F*!i(21),"Object",{defineProperty:i(20).f})},function(t,e,i){Object.defineProperty(e,"__esModule",{value:!0});var o="string",n="boolean",s="number",r={fill:{string:o},stroke:{string:o},strokeWidth:{number:s},__type__:{string:o,object:"object",undefined:"undefined"}},a={animationAutoStart:{boolean:n,undefined:"undefined"},animationInterval:{number:s},animationPreload:{boolean:n},axisColor:{string:o},backgroundColor:r,xBarWidth:{number:s,undefined:"undefined"},yBarWidth:{number:s,undefined:"undefined"},cameraPosition:{distance:{number:s},horizontal:{number:s},vertical:{number:s},__type__:{object:"object"}},xCenter:{string:o},yCenter:{string:o},dataColor:r,dotSizeMinFraction:{number:s},dotSizeMaxFraction:{number:s},dotSizeRatio:{number:s},filterLabel:{string:o},gridColor:{string:o},onclick:{function:"function"},keepAspectRatio:{boolean:n},xLabel:{string:o},yLabel:{string:o},zLabel:{string:o},legendLabel:{string:o},xMin:{number:s,undefined:"undefined"},yMin:{number:s,undefined:"undefined"},zMin:{number:s,undefined:"undefined"},xMax:{number:s,undefined:"undefined"},yMax:{number:s,undefined:"undefined"},zMax:{number:s,undefined:"undefined"},showAnimationControls:{boolean:n,undefined:"undefined"},showGrid:{boolean:n},showLegend:{boolean:n,undefined:"undefined"},showPerspective:{boolean:n},showShadow:{boolean:n},showXAxis:{boolean:n},showYAxis:{boolean:n},showZAxis:{boolean:n},xStep:{number:s,undefined:"undefined"},yStep:{number:s,undefined:"undefined"},zStep:{number:s,undefined:"undefined"},style:{number:s,string:["bar","bar-color","bar-size","dot","dot-line","dot-color","dot-size","line","grid","surface"]},tooltip:{boolean:n,function:"function"},tooltipStyle:{content:{color:{string:o},background:{string:o},border:{string:o},borderRadius:{string:o},boxShadow:{string:o},padding:{string:o},__type__:{object:"object"}},line:{borderLeft:{string:o},height:{string:o},width:{string:o},__type__:{object:"object"}},dot:{border:{string:o},borderRadius:{string:o},height:{string:o},width:{string:o},__type__:{object:"object"}},__type__:{object:"object"}},xValueLabel:{function:"function"},yValueLabel:{function:"function"},zValueLabel:{function:"function"},valueMax:{number:s,undefined:"undefined"},valueMin:{number:s,undefined:"undefined"},verticalRatio:{number:s},height:{string:o},width:{string:o},__type__:{object:"object"}};e.allOptions=a},function(t,e,i){function o(){this.dataTable=null}var n=i(11),s=i(12),r=i(174),a=i(96),h=i(94),d=i(34);o.prototype.initializeData=function(t,e,i){if(void 0!==e){Array.isArray(e)&&(e=new n(e));var o;if(!(e instanceof n||e instanceof s))throw new Error("Array, DataSet, or DataView expected");if(o=e.get(),0!=o.length){this.style=i,this.dataSet&&this.dataSet.off("*",this._onChange),this.dataSet=e,this.dataTable=o;var r=this;this._onChange=function(){t.setData(r.dataSet)},this.dataSet.on("*",this._onChange),this.colX="x",this.colY="y",this.colZ="z";var h=t.hasBars(i);if(h&&(void 0!==t.defaultXBarWidth?this.xBarWidth=t.defaultXBarWidth:this.xBarWidth=this.getSmallestDifference(o,this.colX)||1,void 0!==t.defaultYBarWidth?this.yBarWidth=t.defaultYBarWidth:this.yBarWidth=this.getSmallestDifference(o,this.colY)||1),this._initializeRange(o,this.colX,t,h),this._initializeRange(o,this.colY,t,h),this._initializeRange(o,this.colZ,t,!1),o[0].hasOwnProperty("style")){this.colValue="style";var d=this.getColumnRange(o,this.colValue);this._setRangeDefaults(d,t.defaultValueMin,t.defaultValueMax),this.valueRange=d}this.getDataTable()[0].hasOwnProperty("filter")&&void 0===this.dataFilter&&(this.dataFilter=new a(this,"filter",t),this.dataFilter.setOnLoadCallback(function(){t.redraw()}));return this.dataFilter?this.dataFilter._getDataPoints():this._getDataPoints(this.getDataTable())}}},o.prototype._collectRangeSettings=function(t,e){if(-1==["x","y","z"].indexOf(t))throw new Error("Column '"+t+"' invalid");var i=t.toUpperCase();return{barWidth:this[t+"BarWidth"],min:e["default"+i+"Min"],max:e["default"+i+"Max"],step:e["default"+i+"Step"],range_label:t+"Range",step_label:t+"Step"}},o.prototype._initializeRange=function(t,e,i,o){var n=this._collectRangeSettings(e,i),s=this.getColumnRange(t,e);o&&"z"!=e&&s.expand(n.barWidth/2),this._setRangeDefaults(s,n.min,n.max),this[n.range_label]=s,this[n.step_label]=void 0!==n.step?n.step:s.range()/5},o.prototype.getDistinctValues=function(t,e){void 0===e&&(e=this.dataTable);for(var i=[],o=0;os)&&(o=s)}return o},o.prototype.getColumnRange=function(t,e){for(var i=new r,o=0;o0&&(e[i-1].pointNext=e[i]);return e},o.prototype._checkValueField=function(t){if(this.style===h.STYLE.BARCOLOR||this.style===h.STYLE.BARSIZE||this.style===h.STYLE.DOTCOLOR||this.style===h.STYLE.DOTSIZE){if(void 0===this.colValue)throw new Error("Expected data to have field 'style' for graph style '"+this.style+"'");if(void 0===t[0][this.colValue])throw new Error("Expected data to have field '"+this.colValue+"' for graph style '"+this.style+"'")}},t.exports=o},function(t,e,i){function o(){this.min=void 0,this.max=void 0}o.prototype.adjust=function(t){void 0!==t&&((void 0===this.min||this.min>t)&&(this.min=t),(void 0===this.max||this.maxi)throw new Error("Passed expansion value makes range invalid");this.min=e,this.max=i}},o.prototype.range=function(){return this.max-this.min},o.prototype.center=function(){return(this.min+this.max)/2},t.exports=o},function(t,e,i){var o,n,s;!function(i){n=[],o=i,void 0!==(s="function"==typeof o?o.apply(e,n):o)&&(t.exports=s)}(function(){var t=null;return function e(i,o){function n(t){return t.match(/[^ ]+/g)}function s(e){if("hammer.input"!==e.type){if(e.srcEvent._handled||(e.srcEvent._handled={}),e.srcEvent._handled[e.type])return;e.srcEvent._handled[e.type]=!0}var i=!1;e.stopPropagation=function(){i=!0};var o=e.srcEvent.stopPropagation.bind(e.srcEvent);"function"==typeof o&&(e.srcEvent.stopPropagation=function(){o(),e.stopPropagation()}),e.firstTarget=t;for(var n=t;n&&!i;){var s=n.hammer;if(s)for(var r,a=0;a0?d._handlers[t]=o:(i.off(t,s),delete d._handlers[t]))}),d},d.emit=function(e,o){t=o.target,i.emit(e,o)},d.destroy=function(){var t=i.element.hammer,e=t.indexOf(d);-1!==e&&t.splice(e,1),t.length||delete i.element.hammer,d._handlers={},i.destroy()},d}})},function(t,e,i){var o;!function(n,s,r,a){function h(t,e,i){return setTimeout(p(t,i),e)}function d(t,e,i){return!!Array.isArray(t)&&(l(t,i[e],i),!0)}function l(t,e,i){var o;if(t)if(t.forEach)t.forEach(e,i);else if(t.length!==a)for(o=0;o\s*\(/gm,"{anonymous}()@"):"Unknown Stack Trace",s=n.console&&(n.console.warn||n.console.log);return s&&s.call(n.console,o,i),t.apply(this,arguments)}}function c(t,e,i){var o,n=e.prototype;o=t.prototype=Object.create(n),o.constructor=t,o._super=n,i&&ft(o,i)}function p(t,e){return function(){return t.apply(e,arguments)}}function f(t,e){return typeof t==gt?t.apply(e?e[0]||a:a,e):t}function m(t,e){return t===a?e:t}function v(t,e,i){l(_(e),function(e){t.addEventListener(e,i,!1)})}function g(t,e,i){l(_(e),function(e){t.removeEventListener(e,i,!1)})}function y(t,e){for(;t;){if(t==e)return!0;t=t.parentNode}return!1}function b(t,e){return t.indexOf(e)>-1}function _(t){return t.trim().split(/\s+/g)}function w(t,e,i){if(t.indexOf&&!i)return t.indexOf(e);for(var o=0;oi[e]}):o.sort()),o}function S(t,e){for(var i,o,n=e[0].toUpperCase()+e.slice(1),s=0;s1&&!i.firstMultiple?i.firstMultiple=N(e):1===n&&(i.firstMultiple=!1);var s=i.firstInput,r=i.firstMultiple,a=r?r.center:s.center,h=e.center=R(o);e.timeStamp=_t(),e.deltaTime=e.timeStamp-s.timeStamp,e.angle=F(a,h),e.distance=L(a,h),P(i,e),e.offsetDirection=z(e.deltaX,e.deltaY);var d=A(e.deltaTime,e.deltaX,e.deltaY);e.overallVelocityX=d.x,e.overallVelocityY=d.y,e.overallVelocity=bt(d.x)>bt(d.y)?d.x:d.y,e.scale=r?j(r.pointers,o):1,e.rotation=r?B(r.pointers,o):0,e.maxPointers=i.prevInput?e.pointers.length>i.prevInput.maxPointers?e.pointers.length:i.prevInput.maxPointers:e.pointers.length,I(i,e);var l=t.element;y(e.srcEvent.target,l)&&(l=e.srcEvent.target),e.target=l}function P(t,e){var i=e.center,o=t.offsetDelta||{},n=t.prevDelta||{},s=t.prevInput||{};e.eventType!==Et&&s.eventType!==Pt||(n=t.prevDelta={x:s.deltaX||0,y:s.deltaY||0},o=t.offsetDelta={x:i.x,y:i.y}),e.deltaX=n.x+(i.x-o.x),e.deltaY=n.y+(i.y-o.y)}function I(t,e){var i,o,n,s,r=t.lastInterval||e,h=e.timeStamp-r.timeStamp;if(e.eventType!=It&&(h>Ot||r.velocity===a)){var d=e.deltaX-r.deltaX,l=e.deltaY-r.deltaY,u=A(h,d,l);o=u.x,n=u.y,i=bt(u.x)>bt(u.y)?u.x:u.y,s=z(d,l),t.lastInterval=e}else i=r.velocity,o=r.velocityX,n=r.velocityY,s=r.direction;e.velocity=i,e.velocityX=o,e.velocityY=n,e.direction=s}function N(t){for(var e=[],i=0;i=bt(e)?t<0?Rt:At:e<0?zt:Lt}function L(t,e,i){i||(i=Ht);var o=e[i[0]]-t[i[0]],n=e[i[1]]-t[i[1]];return Math.sqrt(o*o+n*n)}function F(t,e,i){i||(i=Ht);var o=e[i[0]]-t[i[0]],n=e[i[1]]-t[i[1]];return 180*Math.atan2(n,o)/Math.PI}function B(t,e){return F(e[1],e[0],Wt)+F(t[1],t[0],Wt)}function j(t,e){return L(e[0],e[1],Wt)/L(t[0],t[1],Wt)}function H(){this.evEl=Gt,this.evWin=Vt,this.pressed=!1,C.apply(this,arguments)}function W(){this.evEl=Xt,this.evWin=Zt,C.apply(this,arguments),this.store=this.manager.session.pointerEvents=[]}function Y(){this.evTarget=Jt,this.evWin=$t,this.started=!1,C.apply(this,arguments)}function G(t,e){var i=x(t.touches),o=x(t.changedTouches);return e&(Pt|It)&&(i=k(i.concat(o),"identifier",!0)),[i,o]}function V(){this.evTarget=te,this.targetIds={},C.apply(this,arguments)}function U(t,e){var i=x(t.touches),o=this.targetIds;if(e&(Et|Tt)&&1===i.length)return o[i[0].identifier]=!0,[i,i];var n,s,r=x(t.changedTouches),a=[],h=this.target;if(s=i.filter(function(t){return y(t.target,h)}),e===Et)for(n=0;n-1&&o.splice(t,1)};setTimeout(n,ee)}}function K(t){for(var e=t.srcEvent.clientX,i=t.srcEvent.clientY,o=0;o-1&&this.requireFail.splice(e,1),this},hasRequireFailures:function(){return this.requireFail.length>0},canRecognizeWith:function(t){return!!this.simultaneous[t.id]},emit:function(t){function e(e){i.manager.emit(e,t)}var i=this,o=this.state;o=fe&&e(i.options.event+tt(o))},tryEmit:function(t){if(this.canEmit())return this.emit(t);this.state=32},canEmit:function(){for(var t=0;te.threshold&&n&e.direction},attrTest:function(t){return ot.prototype.attrTest.call(this,t)&&(this.state&ce||!(this.state&ce)&&this.directionTest(t))},emit:function(t){this.pX=t.deltaX,this.pY=t.deltaY;var e=et(t.direction);e&&(t.additionalEvent=this.options.event+e),this._super.emit.call(this,t)}}),c(st,ot,{defaults:{event:"pinch",threshold:0,pointers:2},getTouchAction:function(){return[ae]},attrTest:function(t){return this._super.attrTest.call(this,t)&&(Math.abs(t.scale-1)>this.options.threshold||this.state&ce)},emit:function(t){if(1!==t.scale){var e=t.scale<1?"in":"out";t.additionalEvent=this.options.event+e}this._super.emit.call(this,t)}}),c(rt,Q,{defaults:{event:"press",pointers:1,time:251,threshold:9},getTouchAction:function(){return[se]},process:function(t){var e=this.options,i=t.pointers.length===e.pointers,o=t.distancee.time;if(this._input=t,!o||!i||t.eventType&(Pt|It)&&!n)this.reset();else if(t.eventType&Et)this.reset(),this._timer=h(function(){this.state=me,this.tryEmit()},e.time,this);else if(t.eventType&Pt)return me;return 32},reset:function(){clearTimeout(this._timer)},emit:function(t){this.state===me&&(t&&t.eventType&Pt?this.manager.emit(this.options.event+"up",t):(this._input.timeStamp=_t(),this.manager.emit(this.options.event,this._input)))}}),c(at,ot,{defaults:{event:"rotate",threshold:0,pointers:2},getTouchAction:function(){return[ae]},attrTest:function(t){return this._super.attrTest.call(this,t)&&(Math.abs(t.rotation)>this.options.threshold||this.state&ce)}}),c(ht,ot,{defaults:{event:"swipe",threshold:10,velocity:.3,direction:Ft|Bt,pointers:1},getTouchAction:function(){return nt.prototype.getTouchAction.call(this)},attrTest:function(t){var e,i=this.options.direction;return i&(Ft|Bt)?e=t.overallVelocity:i&Ft?e=t.overallVelocityX:i&Bt&&(e=t.overallVelocityY),this._super.attrTest.call(this,t)&&i&t.offsetDirection&&t.distance>this.options.threshold&&t.maxPointers==this.options.pointers&&bt(e)>this.options.velocity&&t.eventType&Pt},emit:function(t){var e=et(t.offsetDirection);e&&this.manager.emit(this.options.event+e,t),this.manager.emit(this.options.event,t)}}),c(dt,Q,{defaults:{event:"tap",pointers:1,taps:1,interval:300,time:250,threshold:9,posThreshold:10},getTouchAction:function(){return[re]},process:function(t){var e=this.options,i=t.pointers.length===e.pointers,o=t.distanced+i?s+=h()+u-i+t.itemSet.options.margin.item.vertical:r=!1,s=Math.min(s,o-i),{shouldScroll:r,scrollOffset:s,itemTop:l}}var a=i(9),h=i(2),d=i(11),l=i(12),u=i(64),c=i(65),p=i(45),f=i(67),m=i(46),v=i(99),g=i(15).printStyle,y=i(105).allOptions,b=i(105).configureOptions,_=i(71).default,w=i(15).default;o.prototype=new c,o.prototype._createConfigurator=function(){return new _(this,this.dom.container,b)},o.prototype.redraw=function(){this.itemSet&&this.itemSet.markDirty({refreshItems:!0}),this._redraw()},o.prototype.setOptions=function(t){if(!0===w.validate(t,y)&&console.log("%cErrors have been found in the supplied options object.",g),c.prototype.setOptions.call(this,t),"type"in t&&t.type!==this.options.type){this.options.type=t.type;var e=this.itemsData;if(e){var i=this.getSelection();this.setItems(null),this.setItems(e),this.setSelection(i)}}},o.prototype.setItems=function(t){var e;e=t?t instanceof d||t instanceof l?t:new d(t,{type:{start:"Date",end:"Date"}}):null,this.itemsData=e,this.itemSet&&this.itemSet.setItems(e)},o.prototype.setGroups=function(t){var e;if(t){var i=function(t){return!1!==t.visible};e=t instanceof d||t instanceof l?new l(t,{filter:i}):new d(t.filter(i))}else e=null;this.groupsData=e,this.itemSet.setGroups(e)},o.prototype.setData=function(t){t&&t.groups&&this.setGroups(t.groups),t&&t.items&&this.setItems(t.items)},o.prototype.setSelection=function(t,e){this.itemSet&&this.itemSet.setSelection(t),e&&e.focus&&this.focus(t,e)},o.prototype.getSelection=function(){return this.itemSet&&this.itemSet.getSelection()||[]},o.prototype.focus=function(t,e){if(this.itemsData&&void 0!=t){var i=Array.isArray(t)?t:[t],o=this.itemsData.getDataSet().get(i,{type:{start:"Date",end:"Date"}}),n=null,s=null;if(o.forEach(function(t){var e=t.start.valueOf(),i="end"in t?t.end.valueOf():t.start.valueOf();(null===n||es)&&(s=i)}),null!==n&&null!==s){var a=this,h=this.itemSet.items[i[0]],d=-1*this._getScrollTop(),l=null,u=function(t,e,i){var o=r(a,h);if(l||(l=o),l.itemTop!=o.itemTop||l.shouldScroll){l.itemTop!=o.itemTop&&o.shouldScroll&&(l=o,d=-1*a._getScrollTop());var n=d,s=l.scrollOffset,u=i?s:n+(s-n)*t;a._setScrollTop(-u),e||a._redraw()}},c=function(){var t=r(a,h);t.shouldScroll&&t.itemTop!=l.itemTop&&(a._setScrollTop(-t.scrollOffset),a._redraw())},p=function(){c(),setTimeout(c,100)},f=(n+s)/2,m=Math.max(this.range.end-this.range.start,1.1*(s-n)),v=!e||void 0===e.animation||e.animation;v||(l={shouldScroll:!1,scrollOffset:-1,itemTop:-1}),this.range.setRange(f-m/2,f+m/2,{animation:v},p,u)}}},o.prototype.fit=function(t,e){var i,o=!t||void 0===t.animation||t.animation,n=this.itemsData&&this.itemsData.getDataSet();1===n.length&&void 0===n.get()[0].end?(i=this.getDataRange(),this.moveTo(i.min.valueOf(),{animation:o},e)):(i=this.getItemRange(),this.range.setRange(i.min,i.max,{animation:o},e))},o.prototype.getItemRange=function(){var t=this.getDataRange(),e=null!==t.min?t.min.valueOf():null,i=null!==t.max?t.max.valueOf():null,o=null,r=null;if(null!=e&&null!=i){var a=i-e;a<=0&&(a=10);var d=a/this.props.center.width,l={},u=0;h.forEach(this.itemSet.items,function(t,e){if(t.groupShowing){l[e]=t.redraw(!0),u=l[e].length}});if(u>0)for(var c=0;ci&&(i=h,r=t)}.bind(this)),o&&r){var p=o.getWidthLeft()+10,f=r.getWidthRight()+10,m=this.props.center.width-p-f;m>0&&(this.options.rtl?(e=n(o)-f*a/m,i=s(r)+p*a/m):(e=n(o)-p*a/m,i=s(r)+f*a/m))}}return{min:null!=e?new Date(e):null,max:null!=i?new Date(i):null}},o.prototype.getDataRange=function(){var t=null,e=null,i=this.itemsData&&this.itemsData.getDataSet();return i&&i.forEach(function(i){var o=h.convert(i.start,"Date").valueOf(),n=h.convert(void 0!=i.end?i.end:i.start,"Date").valueOf();(null===t||oe)&&(e=n)}),{min:null!=t?new Date(t):null,max:null!=e?new Date(e):null}},o.prototype.getEventProperties=function(t){var e,i=t.center?t.center.x:t.clientX,o=t.center?t.center.y:t.clientY;e=this.options.rtl?h.getAbsoluteRight(this.dom.centerContainer)-i:i-h.getAbsoluteLeft(this.dom.centerContainer);var n=o-h.getAbsoluteTop(this.dom.centerContainer),s=this.itemSet.itemFromTarget(t),r=this.itemSet.groupFromTarget(t),a=m.customTimeFromTarget(t),d=this.itemSet.options.snap||null,l=this.body.util.getScale(),u=this.body.util.getStep(),c=this._toTime(e),p=d?d(c,l,u):c,f=h.getTarget(t),v=null;return null!=s?v="item":null!=a?v="custom-time":h.hasParent(f,this.timeAxis.dom.foreground)?v="axis":this.timeAxis2&&h.hasParent(f,this.timeAxis2.dom.foreground)?v="axis":h.hasParent(f,this.itemSet.dom.labelSet)?v="group-label":h.hasParent(f,this.currentTime.bar)?v="current-time":h.hasParent(f,this.dom.center)&&(v="background"),{event:t,item:s?s.id:null,group:r?r.groupId:null,what:v,pageX:t.srcEvent?t.srcEvent.pageX:t.pageX,pageY:t.srcEvent?t.srcEvent.pageY:t.pageY,x:e,y:n,time:c,snappedTime:p}},o.prototype.toggleRollingMode=function(){this.range.rolling?this.range.stopRolling():(void 0==this.options.rollingMode&&this.setOptions(this.options),this.range.startRolling())},t.exports=o},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(19),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(10),u=i(37),c=i(2),p=function(){function t(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1;(0,a.default)(this,t),this.pixelRatio=e,this.generated=!1,this.centerCoordinates={x:144.5,y:144.5},this.r=289*.49,this.color={r:255,g:255,b:255,a:1},this.hueCircle=void 0,this.initialColor={r:255,g:255,b:255,a:1},this.previousColor=void 0,this.applied=!1,this.updateCallback=function(){},this.closeCallback=function(){},this._create()}return(0,d.default)(t,[{key:"insertTo",value:function(t){void 0!==this.hammer&&(this.hammer.destroy(),this.hammer=void 0),this.container=t,this.container.appendChild(this.frame),this._bindHammer(),this._setSize()}},{key:"setUpdateCallback",value:function(t){if("function"!=typeof t)throw new Error("Function attempted to set as colorPicker update callback is not a function.");this.updateCallback=t}},{key:"setCloseCallback",value:function(t){if("function"!=typeof t)throw new Error("Function attempted to set as colorPicker closing callback is not a function.");this.closeCallback=t}},{key:"_isColorString",value:function(t){var e={black:"#000000",navy:"#000080",darkblue:"#00008B",mediumblue:"#0000CD",blue:"#0000FF",darkgreen:"#006400",green:"#008000",teal:"#008080",darkcyan:"#008B8B",deepskyblue:"#00BFFF",darkturquoise:"#00CED1",mediumspringgreen:"#00FA9A",lime:"#00FF00",springgreen:"#00FF7F",aqua:"#00FFFF",cyan:"#00FFFF",midnightblue:"#191970",dodgerblue:"#1E90FF",lightseagreen:"#20B2AA",forestgreen:"#228B22",seagreen:"#2E8B57",darkslategray:"#2F4F4F",limegreen:"#32CD32",mediumseagreen:"#3CB371",turquoise:"#40E0D0",royalblue:"#4169E1",steelblue:"#4682B4",darkslateblue:"#483D8B",mediumturquoise:"#48D1CC",indigo:"#4B0082",darkolivegreen:"#556B2F",cadetblue:"#5F9EA0",cornflowerblue:"#6495ED",mediumaquamarine:"#66CDAA",dimgray:"#696969",slateblue:"#6A5ACD",olivedrab:"#6B8E23",slategray:"#708090",lightslategray:"#778899",mediumslateblue:"#7B68EE",lawngreen:"#7CFC00",chartreuse:"#7FFF00",aquamarine:"#7FFFD4",maroon:"#800000",purple:"#800080",olive:"#808000",gray:"#808080",skyblue:"#87CEEB",lightskyblue:"#87CEFA",blueviolet:"#8A2BE2",darkred:"#8B0000",darkmagenta:"#8B008B",saddlebrown:"#8B4513",darkseagreen:"#8FBC8F",lightgreen:"#90EE90",mediumpurple:"#9370D8",darkviolet:"#9400D3",palegreen:"#98FB98",darkorchid:"#9932CC",yellowgreen:"#9ACD32",sienna:"#A0522D",brown:"#A52A2A",darkgray:"#A9A9A9",lightblue:"#ADD8E6",greenyellow:"#ADFF2F",paleturquoise:"#AFEEEE",lightsteelblue:"#B0C4DE",powderblue:"#B0E0E6",firebrick:"#B22222",darkgoldenrod:"#B8860B",mediumorchid:"#BA55D3",rosybrown:"#BC8F8F",darkkhaki:"#BDB76B",silver:"#C0C0C0",mediumvioletred:"#C71585",indianred:"#CD5C5C",peru:"#CD853F",chocolate:"#D2691E",tan:"#D2B48C",lightgrey:"#D3D3D3",palevioletred:"#D87093",thistle:"#D8BFD8",orchid:"#DA70D6",goldenrod:"#DAA520",crimson:"#DC143C",gainsboro:"#DCDCDC",plum:"#DDA0DD",burlywood:"#DEB887",lightcyan:"#E0FFFF",lavender:"#E6E6FA",darksalmon:"#E9967A",violet:"#EE82EE",palegoldenrod:"#EEE8AA",lightcoral:"#F08080",khaki:"#F0E68C",aliceblue:"#F0F8FF",honeydew:"#F0FFF0",azure:"#F0FFFF",sandybrown:"#F4A460",wheat:"#F5DEB3",beige:"#F5F5DC",whitesmoke:"#F5F5F5",mintcream:"#F5FFFA",ghostwhite:"#F8F8FF",salmon:"#FA8072",antiquewhite:"#FAEBD7",linen:"#FAF0E6",lightgoldenrodyellow:"#FAFAD2",oldlace:"#FDF5E6",red:"#FF0000",fuchsia:"#FF00FF",magenta:"#FF00FF",deeppink:"#FF1493",orangered:"#FF4500",tomato:"#FF6347",hotpink:"#FF69B4",coral:"#FF7F50",darkorange:"#FF8C00",lightsalmon:"#FFA07A",orange:"#FFA500",lightpink:"#FFB6C1",pink:"#FFC0CB",gold:"#FFD700",peachpuff:"#FFDAB9",navajowhite:"#FFDEAD",moccasin:"#FFE4B5",bisque:"#FFE4C4",mistyrose:"#FFE4E1",blanchedalmond:"#FFEBCD",papayawhip:"#FFEFD5",lavenderblush:"#FFF0F5",seashell:"#FFF5EE",cornsilk:"#FFF8DC",lemonchiffon:"#FFFACD",floralwhite:"#FFFAF0",snow:"#FFFAFA",yellow:"#FFFF00",lightyellow:"#FFFFE0",ivory:"#FFFFF0",white:"#FFFFFF"};if("string"==typeof t)return e[t]}},{key:"setColor",value:function(t){var e=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];if("none"!==t){var i=void 0,o=this._isColorString(t);if(void 0!==o&&(t=o),!0===c.isString(t)){if(!0===c.isValidRGB(t)){var n=t.substr(4).substr(0,t.length-5).split(",");i={r:n[0],g:n[1],b:n[2],a:1}}else if(!0===c.isValidRGBA(t)){var r=t.substr(5).substr(0,t.length-6).split(",");i={r:r[0],g:r[1],b:r[2],a:r[3]}}else if(!0===c.isValidHex(t)){var a=c.hexToRGB(t);i={r:a.r,g:a.g,b:a.b,a:1}}}else if(t instanceof Object&&void 0!==t.r&&void 0!==t.g&&void 0!==t.b){var h=void 0!==t.a?t.a:"1.0";i={r:t.r,g:t.g,b:t.b,a:h}}if(void 0===i)throw new Error("Unknown color passed to the colorPicker. Supported are strings: rgb, hex, rgba. Object: rgb ({r:r,g:g,b:b,[a:a]}). Supplied: "+(0,s.default)(t));this._setColor(i,e)}}},{key:"show",value:function(){void 0!==this.closeCallback&&(this.closeCallback(),this.closeCallback=void 0),this.applied=!1,this.frame.style.display="block",this._generateHueCircle()}},{key:"_hide",value:function(){var t=this;!0===(!(arguments.length>0&&void 0!==arguments[0])||arguments[0])&&(this.previousColor=c.extend({},this.color)),!0===this.applied&&this.updateCallback(this.initialColor),this.frame.style.display="none",setTimeout(function(){void 0!==t.closeCallback&&(t.closeCallback(),t.closeCallback=void 0)},0)}},{key:"_save",value:function(){this.updateCallback(this.color),this.applied=!1,this._hide()}},{key:"_apply",value:function(){this.applied=!0,this.updateCallback(this.color),this._updatePicker(this.color)}},{key:"_loadLast",value:function(){void 0!==this.previousColor?this.setColor(this.previousColor,!1):alert("There is no last color to load...")}},{key:"_setColor",value:function(t){!0===(!(arguments.length>1&&void 0!==arguments[1])||arguments[1])&&(this.initialColor=c.extend({},t)),this.color=t;var e=c.RGBToHSV(t.r,t.g,t.b),i=2*Math.PI,o=this.r*e.s,n=this.centerCoordinates.x+o*Math.sin(i*e.h),s=this.centerCoordinates.y+o*Math.cos(i*e.h);this.colorPickerSelector.style.left=n-.5*this.colorPickerSelector.clientWidth+"px",this.colorPickerSelector.style.top=s-.5*this.colorPickerSelector.clientHeight+"px",this._updatePicker(t)}},{key:"_setOpacity",value:function(t){this.color.a=t/100,this._updatePicker(this.color)}},{key:"_setBrightness",value:function(t){var e=c.RGBToHSV(this.color.r,this.color.g,this.color.b);e.v=t/100;var i=c.HSVToRGB(e.h,e.s,e.v);i.a=this.color.a,this.color=i,this._updatePicker()}},{key:"_updatePicker",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.color,e=c.RGBToHSV(t.r,t.g,t.b),i=this.colorPickerCanvas.getContext("2d");void 0===this.pixelRation&&(this.pixelRatio=(window.devicePixelRatio||1)/(i.webkitBackingStorePixelRatio||i.mozBackingStorePixelRatio||i.msBackingStorePixelRatio||i.oBackingStorePixelRatio||i.backingStorePixelRatio||1)),i.setTransform(this.pixelRatio,0,0,this.pixelRatio,0,0);var o=this.colorPickerCanvas.clientWidth,n=this.colorPickerCanvas.clientHeight;i.clearRect(0,0,o,n),i.putImageData(this.hueCircle,0,0),i.fillStyle="rgba(0,0,0,"+(1-e.v)+")",i.circle(this.centerCoordinates.x,this.centerCoordinates.y,this.r),i.fill(),this.brightnessRange.value=100*e.v,this.opacityRange.value=100*t.a,this.initialColorDiv.style.backgroundColor="rgba("+this.initialColor.r+","+this.initialColor.g+","+this.initialColor.b+","+this.initialColor.a+")",this.newColorDiv.style.backgroundColor="rgba("+this.color.r+","+this.color.g+","+this.color.b+","+this.color.a+")"}},{key:"_setSize",value:function(){this.colorPickerCanvas.style.width="100%",this.colorPickerCanvas.style.height="100%",this.colorPickerCanvas.width=289*this.pixelRatio,this.colorPickerCanvas.height=289*this.pixelRatio}},{key:"_create",value:function(){if(this.frame=document.createElement("div"),this.frame.className="vis-color-picker",this.colorPickerDiv=document.createElement("div"),this.colorPickerSelector=document.createElement("div"),this.colorPickerSelector.className="vis-selector",this.colorPickerDiv.appendChild(this.colorPickerSelector),this.colorPickerCanvas=document.createElement("canvas"),this.colorPickerDiv.appendChild(this.colorPickerCanvas),this.colorPickerCanvas.getContext){var t=this.colorPickerCanvas.getContext("2d") +function tt(t){return t&ve?"cancel":t&fe?"end":t&pe?"move":t&ce?"start":""}function et(t){return t==Lt?"down":t==zt?"up":t==Rt?"left":t==At?"right":""}function it(t,e){var i=e.manager;return i?i.get(t):t}function ot(){Q.apply(this,arguments)}function nt(){ot.apply(this,arguments),this.pX=null,this.pY=null}function st(){ot.apply(this,arguments)}function rt(){Q.apply(this,arguments),this._timer=null,this._input=null}function at(){ot.apply(this,arguments)}function ht(){ot.apply(this,arguments)}function dt(){Q.apply(this,arguments),this.pTime=!1,this.pCenter=!1,this._timer=null,this._input=null,this.count=0}function lt(t,e){return e=e||{},e.recognizers=m(e.recognizers,lt.defaults.preset),new ut(t,e)}function ut(t,e){this.options=ft({},lt.defaults,e||{}),this.options.inputTarget=this.options.inputTarget||t,this.handlers={},this.session={},this.recognizers=[],this.oldCssProps={},this.element=t,this.input=O(this),this.touchAction=new J(this,this.options.touchAction),ct(this,!0),l(this.options.recognizers,function(t){var e=this.add(new t[0](t[1]));t[2]&&e.recognizeWith(t[2]),t[3]&&e.requireFailure(t[3])},this)}function ct(t,e){var i=t.element;if(i.style){var o;l(t.options.cssProps,function(n,s){o=S(i.style,s),e?(t.oldCssProps[o]=i.style[o],i.style[o]=n):i.style[o]=t.oldCssProps[o]||""}),e||(t.oldCssProps={})}}function pt(t,e){var i=s.createEvent("Event");i.initEvent(t,!0,!0),i.gesture=e,e.target.dispatchEvent(i)}var ft,mt=["","webkit","Moz","MS","ms","o"],vt=s.createElement("div"),gt="function",yt=Math.round,bt=Math.abs,_t=Date.now;ft="function"!=typeof Object.assign?function(t){if(t===a||null===t)throw new TypeError("Cannot convert undefined or null to object");for(var e=Object(t),i=1;i-1&&this.requireFail.splice(e,1),this},hasRequireFailures:function(){return this.requireFail.length>0},canRecognizeWith:function(t){return!!this.simultaneous[t.id]},emit:function(t){function e(e){i.manager.emit(e,t)}var i=this,o=this.state;o=fe&&e(io.cucumber.core.event+tt(o))},tryEmit:function(t){if(this.canEmit())return this.emit(t);this.state=32},canEmit:function(){for(var t=0; te.threshold&&n&e.direction},attrTest:function(t){return ot.prototype.attrTest.call(this,t)&&(this.state&ce||!(this.state&ce)&&this.directionTest(t))},emit:function(t){this.pX=t.deltaX,this.pY=t.deltaY;var e=et(t.direction);e&&(t.additionalEvent=io.cucumber.core.event+e),this._super.emit.call(this,t)}}),c(st,ot,{defaults:{event:"pinch",threshold:0,pointers:2},getTouchAction:function(){return[ae]},attrTest:function(t){return this._super.attrTest.call(this,t)&&(Math.abs(t.scale-1)>this.options.threshold||this.state&ce)},emit:function(t){if(1!==t.scale){var e=t.scale<1?"in":"out";t.additionalEvent=io.cucumber.core.event+e}this._super.emit.call(this,t)}}),c(rt,Q,{defaults:{event:"press",pointers:1,time:251,threshold:9},getTouchAction:function(){return[se]},process:function(t){var e=this.options,i=t.pointers.length===e.pointers,o=t.distancee.time;if(this._input=t,!o||!i||t.eventType&(Pt|It)&&!n)this.reset();else if(t.eventType&Et)this.reset(),this._timer=h(function(){this.state=me,this.tryEmit()},e.time,this);else if(t.eventType&Pt)return me;return 32},reset:function(){clearTimeout(this._timer)},emit:function(t){this.state===me&&(t&&t.eventType&Pt?this.manager.emit(io.cucumber.core.event+"up",t):(this._input.timeStamp=_t(),this.manager.emit(io.cucumber.core.event,this._input)))}}),c(at,ot,{defaults:{event:"rotate",threshold:0,pointers:2},getTouchAction:function(){return[ae]},attrTest:function(t){return this._super.attrTest.call(this,t)&&(Math.abs(t.rotation)>this.options.threshold||this.state&ce)}}),c(ht,ot,{defaults:{event:"swipe",threshold:10,velocity:.3,direction:Ft|Bt,pointers:1},getTouchAction:function(){return nt.prototype.getTouchAction.call(this)},attrTest:function(t){var e,i=this.options.direction;return i&(Ft|Bt)?e=t.overallVelocity:i&Ft?e=t.overallVelocityX:i&Bt&&(e=t.overallVelocityY),this._super.attrTest.call(this,t)&&i&t.offsetDirection&&t.distance>this.options.threshold&&t.maxPointers==this.options.pointers&&bt(e)>this.options.velocity&&t.eventType&Pt},emit:function(t){var e=et(t.offsetDirection);e&&this.manager.emit(io.cucumber.core.event+e,t),this.manager.emit(io.cucumber.core.event,t)}}),c(dt,Q,{defaults:{event:"tap",pointers:1,taps:1,interval:300,time:250,threshold:9,posThreshold:10},getTouchAction:function(){return[re]},process:function(t){var e=this.options,i=t.pointers.length===e.pointers,o=t.distanced+i?s+=h()+u-i+t.itemSet.options.margin.item.vertical:r=!1,s=Math.min(s,o-i),{shouldScroll:r,scrollOffset:s,itemTop:l}}var a=i(9),h=i(2),d=i(11),l=i(12),u=i(64),c=i(65),p=i(45),f=i(67),m=i(46),v=i(99),g=i(15).printStyle,y=i(105).allOptions,b=i(105).configureOptions,_=i(71).default,w=i(15).default;o.prototype=new c,o.prototype._createConfigurator=function(){return new _(this,this.dom.container,b)},o.prototype.redraw=function(){this.itemSet&&this.itemSet.markDirty({refreshItems:!0}),this._redraw()},o.prototype.setOptions=function(t){if(!0===w.validate(t,y)&&console.log("%cErrors have been found in the supplied options object.",g),c.prototype.setOptions.call(this,t),"type"in t&&t.type!==this.options.type){this.options.type=t.type;var e=this.itemsData;if(e){var i=this.getSelection();this.setItems(null),this.setItems(e),this.setSelection(i)}}},o.prototype.setItems=function(t){var e;e=t?t instanceof d||t instanceof l?t:new d(t,{type:{start:"Date",end:"Date"}}):null,this.itemsData=e,this.itemSet&&this.itemSet.setItems(e)},o.prototype.setGroups=function(t){var e;if(t){var i=function(t){return!1!==t.visible};e=t instanceof d||t instanceof l?new l(t,{filter:i}):new d(t.filter(i))}else e=null;this.groupsData=e,this.itemSet.setGroups(e)},o.prototype.setData=function(t){t&&t.groups&&this.setGroups(t.groups),t&&t.items&&this.setItems(t.items)},o.prototype.setSelection=function(t,e){this.itemSet&&this.itemSet.setSelection(t),e&&e.focus&&this.focus(t,e)},o.prototype.getSelection=function(){return this.itemSet&&this.itemSet.getSelection()||[]},o.prototype.focus=function(t,e){if(this.itemsData&&void 0!=t){var i=Array.isArray(t)?t:[t],o=this.itemsData.getDataSet().get(i,{type:{start:"Date",end:"Date"}}),n=null,s=null;if(o.forEach(function(t){var e=t.start.valueOf(),i="end"in t?t.end.valueOf():t.start.valueOf();(null===n||es)&&(s=i)}),null!==n&&null!==s){var a=this,h=this.itemSet.items[i[0]],d=-1*this._getScrollTop(),l=null,u=function(t,e,i){var o=r(a,h);if(l||(l=o),l.itemTop!=o.itemTop||l.shouldScroll){l.itemTop!=o.itemTop&&o.shouldScroll&&(l=o,d=-1*a._getScrollTop());var n=d,s=l.scrollOffset,u=i?s:n+(s-n)*t;a._setScrollTop(-u),e||a._redraw()}},c=function(){var t=r(a,h);t.shouldScroll&&t.itemTop!=l.itemTop&&(a._setScrollTop(-t.scrollOffset),a._redraw())},p=function(){c(),setTimeout(c,100)},f=(n+s)/2,m=Math.max(this.range.end-this.range.start,1.1*(s-n)),v=!e||void 0===e.animation||e.animation;v||(l={shouldScroll:!1,scrollOffset:-1,itemTop:-1}),this.range.setRange(f-m/2,f+m/2,{animation:v},p,u)}}},o.prototype.fit=function(t,e){var i,o=!t||void 0===t.animation||t.animation,n=this.itemsData&&this.itemsData.getDataSet();1===n.length&&void 0===n.get()[0].end?(i=this.getDataRange(),this.moveTo(i.min.valueOf(),{animation:o},e)):(i=this.getItemRange(),this.range.setRange(i.min,i.max,{animation:o},e))},o.prototype.getItemRange=function(){var t=this.getDataRange(),e=null!==t.min?t.min.valueOf():null,i=null!==t.max?t.max.valueOf():null,o=null,r=null;if(null!=e&&null!=i){var a=i-e;a<=0&&(a=10);var d=a/this.props.center.width,l={},u=0;h.forEach(this.itemSet.items,function(t,e){if(t.groupShowing){l[e]=t.redraw(!0),u=l[e].length}});if(u>0)for(var c=0;ci&&(i=h,r=t)}.bind(this)),o&&r){var p=o.getWidthLeft()+10,f=r.getWidthRight()+10,m=this.props.center.width-p-f;m>0&&(this.options.rtl?(e=n(o)-f*a/m,i=s(r)+p*a/m):(e=n(o)-p*a/m,i=s(r)+f*a/m))}}return{min:null!=e?new Date(e):null,max:null!=i?new Date(i):null}},o.prototype.getDataRange=function(){var t=null,e=null,i=this.itemsData&&this.itemsData.getDataSet();return i&&i.forEach(function(i){var o=h.convert(i.start,"Date").valueOf(),n=h.convert(void 0!=i.end?i.end:i.start,"Date").valueOf();(null===t||oe)&&(e=n)}),{min:null!=t?new Date(t):null,max:null!=e?new Date(e):null}},o.prototype.getEventProperties=function(t){var e,i=t.center?t.center.x:t.clientX,o=t.center?t.center.y:t.clientY;e=this.options.rtl?h.getAbsoluteRight(this.dom.centerContainer)-i:i-h.getAbsoluteLeft(this.dom.centerContainer);var n=o-h.getAbsoluteTop(this.dom.centerContainer),s=this.itemSet.itemFromTarget(t),r=this.itemSet.groupFromTarget(t),a=m.customTimeFromTarget(t),d=this.itemSet.options.snap||null,l=this.body.util.getScale(),u=this.body.util.getStep(),c=this._toTime(e),p=d?d(c,l,u):c,f=h.getTarget(t),v=null;return null!=s?v="item":null!=a?v="custom-time":h.hasParent(f,this.timeAxis.dom.foreground)?v="axis":this.timeAxis2&&h.hasParent(f,this.timeAxis2.dom.foreground)?v="axis":h.hasParent(f,this.itemSet.dom.labelSet)?v="group-label":h.hasParent(f,this.currentTime.bar)?v="current-time":h.hasParent(f,this.dom.center)&&(v="background"),{event:t,item:s?s.id:null,group:r?r.groupId:null,what:v,pageX:t.srcEvent?t.srcEvent.pageX:t.pageX,pageY:t.srcEvent?t.srcEvent.pageY:t.pageY,x:e,y:n,time:c,snappedTime:p}},o.prototype.toggleRollingMode=function(){this.range.rolling?this.range.stopRolling():(void 0==this.options.rollingMode&&this.setOptions(this.options),this.range.startRolling())},t.exports=o},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(19),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(10),u=i(37),c=i(2),p=function(){function t(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1;(0,a.default)(this,t),this.pixelRatio=e,this.generated=!1,this.centerCoordinates={x:144.5,y:144.5},this.r=289*.49,this.color={r:255,g:255,b:255,a:1},this.hueCircle=void 0,this.initialColor={r:255,g:255,b:255,a:1},this.previousColor=void 0,this.applied=!1,this.updateCallback=function(){},this.closeCallback=function(){},this._create()}return(0,d.default)(t,[{key:"insertTo",value:function(t){void 0!==this.hammer&&(this.hammer.destroy(),this.hammer=void 0),this.container=t,this.container.appendChild(this.frame),this._bindHammer(),this._setSize()}},{key:"setUpdateCallback",value:function(t){if("function"!=typeof t)throw new Error("Function attempted to set as colorPicker update callback is not a function.");this.updateCallback=t}},{key:"setCloseCallback",value:function(t){if("function"!=typeof t)throw new Error("Function attempted to set as colorPicker closing callback is not a function.");this.closeCallback=t}},{key:"_isColorString",value:function(t){var e={black:"#000000",navy:"#000080",darkblue:"#00008B",mediumblue:"#0000CD",blue:"#0000FF",darkgreen:"#006400",green:"#008000",teal:"#008080",darkcyan:"#008B8B",deepskyblue:"#00BFFF",darkturquoise:"#00CED1",mediumspringgreen:"#00FA9A",lime:"#00FF00",springgreen:"#00FF7F",aqua:"#00FFFF",cyan:"#00FFFF",midnightblue:"#191970",dodgerblue:"#1E90FF",lightseagreen:"#20B2AA",forestgreen:"#228B22",seagreen:"#2E8B57",darkslategray:"#2F4F4F",limegreen:"#32CD32",mediumseagreen:"#3CB371",turquoise:"#40E0D0",royalblue:"#4169E1",steelblue:"#4682B4",darkslateblue:"#483D8B",mediumturquoise:"#48D1CC",indigo:"#4B0082",darkolivegreen:"#556B2F",cadetblue:"#5F9EA0",cornflowerblue:"#6495ED",mediumaquamarine:"#66CDAA",dimgray:"#696969",slateblue:"#6A5ACD",olivedrab:"#6B8E23",slategray:"#708090",lightslategray:"#778899",mediumslateblue:"#7B68EE",lawngreen:"#7CFC00",chartreuse:"#7FFF00",aquamarine:"#7FFFD4",maroon:"#800000",purple:"#800080",olive:"#808000",gray:"#808080",skyblue:"#87CEEB",lightskyblue:"#87CEFA",blueviolet:"#8A2BE2",darkred:"#8B0000",darkmagenta:"#8B008B",saddlebrown:"#8B4513",darkseagreen:"#8FBC8F",lightgreen:"#90EE90",mediumpurple:"#9370D8",darkviolet:"#9400D3",palegreen:"#98FB98",darkorchid:"#9932CC",yellowgreen:"#9ACD32",sienna:"#A0522D",brown:"#A52A2A",darkgray:"#A9A9A9",lightblue:"#ADD8E6",greenyellow:"#ADFF2F",paleturquoise:"#AFEEEE",lightsteelblue:"#B0C4DE",powderblue:"#B0E0E6",firebrick:"#B22222",darkgoldenrod:"#B8860B",mediumorchid:"#BA55D3",rosybrown:"#BC8F8F",darkkhaki:"#BDB76B",silver:"#C0C0C0",mediumvioletred:"#C71585",indianred:"#CD5C5C",peru:"#CD853F",chocolate:"#D2691E",tan:"#D2B48C",lightgrey:"#D3D3D3",palevioletred:"#D87093",thistle:"#D8BFD8",orchid:"#DA70D6",goldenrod:"#DAA520",crimson:"#DC143C",gainsboro:"#DCDCDC",plum:"#DDA0DD",burlywood:"#DEB887",lightcyan:"#E0FFFF",lavender:"#E6E6FA",darksalmon:"#E9967A",violet:"#EE82EE",palegoldenrod:"#EEE8AA",lightcoral:"#F08080",khaki:"#F0E68C",aliceblue:"#F0F8FF",honeydew:"#F0FFF0",azure:"#F0FFFF",sandybrown:"#F4A460",wheat:"#F5DEB3",beige:"#F5F5DC",whitesmoke:"#F5F5F5",mintcream:"#F5FFFA",ghostwhite:"#F8F8FF",salmon:"#FA8072",antiquewhite:"#FAEBD7",linen:"#FAF0E6",lightgoldenrodyellow:"#FAFAD2",oldlace:"#FDF5E6",red:"#FF0000",fuchsia:"#FF00FF",magenta:"#FF00FF",deeppink:"#FF1493",orangered:"#FF4500",tomato:"#FF6347",hotpink:"#FF69B4",coral:"#FF7F50",darkorange:"#FF8C00",lightsalmon:"#FFA07A",orange:"#FFA500",lightpink:"#FFB6C1",pink:"#FFC0CB",gold:"#FFD700",peachpuff:"#FFDAB9",navajowhite:"#FFDEAD",moccasin:"#FFE4B5",bisque:"#FFE4C4",mistyrose:"#FFE4E1",blanchedalmond:"#FFEBCD",papayawhip:"#FFEFD5",lavenderblush:"#FFF0F5",seashell:"#FFF5EE",cornsilk:"#FFF8DC",lemonchiffon:"#FFFACD",floralwhite:"#FFFAF0",snow:"#FFFAFA",yellow:"#FFFF00",lightyellow:"#FFFFE0",ivory:"#FFFFF0",white:"#FFFFFF"};if("string"==typeof t)return e[t]}},{key:"setColor",value:function(t){var e=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];if("none"!==t){var i=void 0,o=this._isColorString(t);if(void 0!==o&&(t=o),!0===c.isString(t)){if(!0===c.isValidRGB(t)){var n=t.substr(4).substr(0,t.length-5).split(",");i={r:n[0],g:n[1],b:n[2],a:1}}else if(!0===c.isValidRGBA(t)){var r=t.substr(5).substr(0,t.length-6).split(",");i={r:r[0],g:r[1],b:r[2],a:r[3]}}else if(!0===c.isValidHex(t)){var a=c.hexToRGB(t);i={r:a.r,g:a.g,b:a.b,a:1}}}else if(t instanceof Object&&void 0!==t.r&&void 0!==t.g&&void 0!==t.b){var h=void 0!==t.a?t.a:"1.0";i={r:t.r,g:t.g,b:t.b,a:h}}if(void 0===i)throw new Error("Unknown color passed to the colorPicker. Supported are strings: rgb, hex, rgba. Object: rgb ({r:r,g:g,b:b,[a:a]}). Supplied: "+(0,s.default)(t));this._setColor(i,e)}}},{key:"show",value:function(){void 0!==this.closeCallback&&(this.closeCallback(),this.closeCallback=void 0),this.applied=!1,this.frame.style.display="block",this._generateHueCircle()}},{key:"_hide",value:function(){var t=this;!0===(!(arguments.length>0&&void 0!==arguments[0])||arguments[0])&&(this.previousColor=c.extend({},this.color)),!0===this.applied&&this.updateCallback(this.initialColor),this.frame.style.display="none",setTimeout(function(){void 0!==t.closeCallback&&(t.closeCallback(),t.closeCallback=void 0)},0)}},{key:"_save",value:function(){this.updateCallback(this.color),this.applied=!1,this._hide()}},{key:"_apply",value:function(){this.applied=!0,this.updateCallback(this.color),this._updatePicker(this.color)}},{key:"_loadLast",value:function(){void 0!==this.previousColor?this.setColor(this.previousColor,!1):alert("There is no last color to load...")}},{key:"_setColor",value:function(t){!0===(!(arguments.length>1&&void 0!==arguments[1])||arguments[1])&&(this.initialColor=c.extend({},t)),this.color=t;var e=c.RGBToHSV(t.r,t.g,t.b),i=2*Math.PI,o=this.r*e.s,n=this.centerCoordinates.x+o*Math.sin(i*e.h),s=this.centerCoordinates.y+o*Math.cos(i*e.h);this.colorPickerSelector.style.left=n-.5*this.colorPickerSelector.clientWidth+"px",this.colorPickerSelector.style.top=s-.5*this.colorPickerSelector.clientHeight+"px",this._updatePicker(t)}},{key:"_setOpacity",value:function(t){this.color.a=t/100,this._updatePicker(this.color)}},{key:"_setBrightness",value:function(t){var e=c.RGBToHSV(this.color.r,this.color.g,this.color.b);e.v=t/100;var i=c.HSVToRGB(e.h,e.s,e.v);i.a=this.color.a,this.color=i,this._updatePicker()}},{key:"_updatePicker",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.color,e=c.RGBToHSV(t.r,t.g,t.b),i=this.colorPickerCanvas.getContext("2d");void 0===this.pixelRation&&(this.pixelRatio=(window.devicePixelRatio||1)/(i.webkitBackingStorePixelRatio||i.mozBackingStorePixelRatio||i.msBackingStorePixelRatio||i.oBackingStorePixelRatio||i.backingStorePixelRatio||1)),i.setTransform(this.pixelRatio,0,0,this.pixelRatio,0,0);var o=this.colorPickerCanvas.clientWidth,n=this.colorPickerCanvas.clientHeight;i.clearRect(0,0,o,n),i.putImageData(this.hueCircle,0,0),i.fillStyle="rgba(0,0,0,"+(1-e.v)+")",i.circle(this.centerCoordinates.x,this.centerCoordinates.y,this.r),i.fill(),this.brightnessRange.value=100*e.v,this.opacityRange.value=100*t.a,this.initialColorDiv.style.backgroundColor="rgba("+this.initialColor.r+","+this.initialColor.g+","+this.initialColor.b+","+this.initialColor.a+")",this.newColorDiv.style.backgroundColor="rgba("+this.color.r+","+this.color.g+","+this.color.b+","+this.color.a+")"}},{key:"_setSize",value:function(){this.colorPickerCanvas.style.width="100%",this.colorPickerCanvas.style.height="100%",this.colorPickerCanvas.width=289*this.pixelRatio,this.colorPickerCanvas.height=289*this.pixelRatio}},{key:"_create",value:function(){if(this.frame=document.createElement("div"),this.frame.className="vis-color-picker",this.colorPickerDiv=document.createElement("div"),this.colorPickerSelector=document.createElement("div"),this.colorPickerSelector.className="vis-selector",this.colorPickerDiv.appendChild(this.colorPickerSelector),this.colorPickerCanvas=document.createElement("canvas"),this.colorPickerDiv.appendChild(this.colorPickerCanvas),this.colorPickerCanvas.getContext){var t=this.colorPickerCanvas.getContext("2d") ;this.pixelRatio=(window.devicePixelRatio||1)/(t.webkitBackingStorePixelRatio||t.mozBackingStorePixelRatio||t.msBackingStorePixelRatio||t.oBackingStorePixelRatio||t.backingStorePixelRatio||1),this.colorPickerCanvas.getContext("2d").setTransform(this.pixelRatio,0,0,this.pixelRatio,0,0)}else{var e=document.createElement("DIV");e.style.color="red",e.style.fontWeight="bold",e.style.padding="10px",e.innerHTML="Error: your browser does not support HTML canvas",this.colorPickerCanvas.appendChild(e)}this.colorPickerDiv.className="vis-color",this.opacityDiv=document.createElement("div"),this.opacityDiv.className="vis-opacity",this.brightnessDiv=document.createElement("div"),this.brightnessDiv.className="vis-brightness",this.arrowDiv=document.createElement("div"),this.arrowDiv.className="vis-arrow",this.opacityRange=document.createElement("input");try{this.opacityRange.type="range",this.opacityRange.min="0",this.opacityRange.max="100"}catch(t){}this.opacityRange.value="100",this.opacityRange.className="vis-range",this.brightnessRange=document.createElement("input");try{this.brightnessRange.type="range",this.brightnessRange.min="0",this.brightnessRange.max="100"}catch(t){}this.brightnessRange.value="100",this.brightnessRange.className="vis-range",this.opacityDiv.appendChild(this.opacityRange),this.brightnessDiv.appendChild(this.brightnessRange);var i=this;this.opacityRange.onchange=function(){i._setOpacity(this.value)},this.opacityRange.oninput=function(){i._setOpacity(this.value)},this.brightnessRange.onchange=function(){i._setBrightness(this.value)},this.brightnessRange.oninput=function(){i._setBrightness(this.value)},this.brightnessLabel=document.createElement("div"),this.brightnessLabel.className="vis-label vis-brightness",this.brightnessLabel.innerHTML="brightness:",this.opacityLabel=document.createElement("div"),this.opacityLabel.className="vis-label vis-opacity",this.opacityLabel.innerHTML="opacity:",this.newColorDiv=document.createElement("div"),this.newColorDiv.className="vis-new-color",this.newColorDiv.innerHTML="new",this.initialColorDiv=document.createElement("div"),this.initialColorDiv.className="vis-initial-color",this.initialColorDiv.innerHTML="initial",this.cancelButton=document.createElement("div"),this.cancelButton.className="vis-button vis-cancel",this.cancelButton.innerHTML="cancel",this.cancelButton.onclick=this._hide.bind(this,!1),this.applyButton=document.createElement("div"),this.applyButton.className="vis-button vis-apply",this.applyButton.innerHTML="apply",this.applyButton.onclick=this._apply.bind(this),this.saveButton=document.createElement("div"),this.saveButton.className="vis-button vis-save",this.saveButton.innerHTML="save",this.saveButton.onclick=this._save.bind(this),this.loadButton=document.createElement("div"),this.loadButton.className="vis-button vis-load",this.loadButton.innerHTML="load last",this.loadButton.onclick=this._loadLast.bind(this),this.frame.appendChild(this.colorPickerDiv),this.frame.appendChild(this.arrowDiv),this.frame.appendChild(this.brightnessLabel),this.frame.appendChild(this.brightnessDiv),this.frame.appendChild(this.opacityLabel),this.frame.appendChild(this.opacityDiv),this.frame.appendChild(this.newColorDiv),this.frame.appendChild(this.initialColorDiv),this.frame.appendChild(this.cancelButton),this.frame.appendChild(this.applyButton),this.frame.appendChild(this.saveButton),this.frame.appendChild(this.loadButton)}},{key:"_bindHammer",value:function(){var t=this;this.drag={},this.pinch={},this.hammer=new l(this.colorPickerCanvas),this.hammer.get("pinch").set({enable:!0}),u.onTouch(this.hammer,function(e){t._moveSelector(e)}),this.hammer.on("tap",function(e){t._moveSelector(e)}),this.hammer.on("panstart",function(e){t._moveSelector(e)}),this.hammer.on("panmove",function(e){t._moveSelector(e)}),this.hammer.on("panend",function(e){t._moveSelector(e)})}},{key:"_generateHueCircle",value:function(){if(!1===this.generated){var t=this.colorPickerCanvas.getContext("2d");void 0===this.pixelRation&&(this.pixelRatio=(window.devicePixelRatio||1)/(t.webkitBackingStorePixelRatio||t.mozBackingStorePixelRatio||t.msBackingStorePixelRatio||t.oBackingStorePixelRatio||t.backingStorePixelRatio||1)),t.setTransform(this.pixelRatio,0,0,this.pixelRatio,0,0);var e=this.colorPickerCanvas.clientWidth,i=this.colorPickerCanvas.clientHeight;t.clearRect(0,0,e,i);var o=void 0,n=void 0,s=void 0,r=void 0;this.centerCoordinates={x:.5*e,y:.5*i},this.r=.49*e;var a=2*Math.PI/360,h=1/this.r,d=void 0;for(s=0;s<360;s++)for(r=0;rr?r:t,e=null==e?r:e0&&l.push(u.screenToValue(n)),!p.hidden&&this.itemsData.length>0&&l.push(p.screenToValue(n)),{event:t,what:d,pageX:t.srcEvent?t.srcEvent.pageX:t.pageX,pageY:t.srcEvent?t.srcEvent.pageY:t.pageY,x:o,y:n,time:r,value:l}},o.prototype._createConfigurator=function(){return new g(this,this.dom.container,v)},t.exports=o},function(t,e,i){e.util=i(2),e.DOMutil=i(14),e.DataSet=i(11),e.DataView=i(12),e.Queue=i(43),e.Network=i(182),e.network={Images:i(116),dotparser:i(114),gephiParser:i(115),allOptions:i(122)},e.network.convertDot=function(t){return e.network.dotparser.DOTToGraph(t)},e.network.convertGephi=function(t,i){return e.network.gephiParser.parseGephi(t,i)},e.moment=i(9),e.Hammer=i(10),e.keycharm=i(35)},function(t,e,i){function o(t,e,i){var n=this;if(!(this instanceof o))throw new SyntaxError("Constructor must be called with the new operator");this.options={},this.defaultOptions={locale:"en",locales:d,clickToUse:!1},s.extend(this.options,this.defaultOptions),this.body={container:t,nodes:{},nodeIndices:[],edges:{},edgeIndices:[],emitter:{on:this.on.bind(this),off:this.off.bind(this),emit:this.emit.bind(this),once:this.once.bind(this)},eventListeners:{onTap:function(){},onTouch:function(){},onDoubleTap:function(){},onHold:function(){},onDragStart:function(){},onDrag:function(){},onDragEnd:function(){},onMouseWheel:function(){},onPinch:function(){},onMouseMove:function(){},onRelease:function(){},onContext:function(){}},data:{nodes:null,edges:null},functions:{createNode:function(){},createEdge:function(){},getPointer:function(){}},modules:{},view:{scale:1,translation:{x:0,y:0}}},this.bindEventListeners(),this.images=new l(function(){return n.body.emitter.emit("_requestRedraw")}),this.groups=new u,this.canvas=new g(this.body),this.selectionHandler=new _(this.body,this.canvas),this.interactionHandler=new b(this.body,this.canvas,this.selectionHandler),this.view=new y(this.body,this.canvas),this.renderer=new v(this.body,this.canvas),this.physics=new f(this.body),this.layoutEngine=new w(this.body),this.clustering=new m(this.body),this.manipulation=new x(this.body,this.canvas,this.selectionHandler),this.nodesHandler=new c(this.body,this.images,this.groups,this.layoutEngine),this.edgesHandler=new p(this.body,this.images,this.groups),this.body.modules.kamadaKawai=new T(this.body,150,.05),this.body.modules.clustering=this.clustering,this.canvas._create(),this.setOptions(i),this.setData(e)}i(183);var n=i(44),s=i(2),r=i(114),a=i(115),h=i(97),d=i(184),l=i(116).default,u=i(186).default,c=i(187).default,p=i(214).default,f=i(220).default,m=i(227).default,v=i(229).default,g=i(230).default,y=i(231).default,b=i(232).default,_=i(234).default,w=i(235).default,x=i(237).default,k=i(71).default,S=i(15).default,D=i(15),M=D.printStyle,C=i(122),O=C.allOptions,E=C.configureOptions,T=i(238).default;n(o.prototype),o.prototype.setOptions=function(t){var e=this;if(void 0!==t){!0===S.validate(t,O)&&console.log("%cErrors have been found in the supplied options object.",M);var i=["locale","locales","clickToUse"];if(s.selectiveDeepExtend(i,this.options,t),t=this.layoutEngine.setOptions(t.layout,t),this.canvas.setOptions(t),this.groups.setOptions(t.groups),this.nodesHandler.setOptions(t.nodes),this.edgesHandler.setOptions(t.edges),this.physics.setOptions(t.physics),this.manipulation.setOptions(t.manipulation,t,this.options),this.interactionHandler.setOptions(t.interaction),this.renderer.setOptions(t.interaction),this.selectionHandler.setOptions(t.interaction),void 0!==t.groups&&this.body.emitter.emit("refreshNodes"),"configure"in t&&(this.configurator||(this.configurator=new k(this,this.body.container,E,this.canvas.pixelRatio)),this.configurator.setOptions(t.configure)),this.configurator&&!0===this.configurator.options.enabled){var o={nodes:{},edges:{},layout:{},interaction:{},manipulation:{},physics:{},global:{}};s.deepExtend(o.nodes,this.nodesHandler.options),s.deepExtend(o.edges,this.edgesHandler.options),s.deepExtend(o.layout,this.layoutEngine.options),s.deepExtend(o.interaction,this.selectionHandler.options),s.deepExtend(o.interaction,this.renderer.options),s.deepExtend(o.interaction,this.interactionHandler.options),s.deepExtend(o.manipulation,this.manipulation.options),s.deepExtend(o.physics,this.physics.options),s.deepExtend(o.global,this.canvas.options),s.deepExtend(o.global,this.options),this.configurator.setModuleOptions(o)}void 0!==t.clickToUse?!0===t.clickToUse?void 0===this.activator&&(this.activator=new h(this.canvas.frame),this.activator.on("change",function(){e.body.emitter.emit("activate")})):(void 0!==this.activator&&(this.activator.destroy(),delete this.activator),this.body.emitter.emit("activate")):this.body.emitter.emit("activate"),this.canvas.setSize(),this.body.emitter.emit("startSimulation")}},o.prototype._updateVisibleIndices=function(){var t=this.body.nodes,e=this.body.edges;this.body.nodeIndices=[],this.body.edgeIndices=[];for(var i in t)t.hasOwnProperty(i)&&(this.clustering._isClusteredNode(i)||!1!==t[i].options.hidden||this.body.nodeIndices.push(t[i].id));for(var o in e)if(e.hasOwnProperty(o)){var n=e[o],s=t[n.fromId],r=t[n.toId],a=void 0!==s&&void 0!==r,h=!this.clustering._isClusteredEdge(o)&&!1===n.options.hidden&&a&&!1===s.options.hidden&&!1===r.options.hidden;h&&this.body.edgeIndices.push(n.id)}},o.prototype.bindEventListeners=function(){var t=this;this.body.emitter.on("_dataChanged",function(){t.edgesHandler._updateState(),t.body.emitter.emit("_dataUpdated")}),this.body.emitter.on("_dataUpdated",function(){t.clustering._updateState(),t._updateVisibleIndices(),t._updateValueRange(t.body.nodes),t._updateValueRange(t.body.edges),t.body.emitter.emit("startSimulation"),t.body.emitter.emit("_requestRedraw")})},o.prototype.setData=function(t){if(this.body.emitter.emit("resetPhysics"),this.body.emitter.emit("_resetData"),this.selectionHandler.unselectAll(),t&&t.dot&&(t.nodes||t.edges))throw new SyntaxError('Data must contain either parameter "dot" or parameter pair "nodes" and "edges", but not both.');if(this.setOptions(t&&t.options),t&&t.dot){console.log("The dot property has been deprecated. Please use the static convertDot method to convert DOT into vis.network format and use the normal data format with nodes and edges. This converter is used like this: var data = vis.network.convertDot(dotString);");var e=r.DOTToGraph(t.dot);return void this.setData(e)}if(t&&t.gephi){console.log("The gephi property has been deprecated. Please use the static convertGephi method to convert gephi into vis.network format and use the normal data format with nodes and edges. This converter is used like this: var data = vis.network.convertGephi(gephiJson);");var i=a.parseGephi(t.gephi);return void this.setData(i)}this.nodesHandler.setData(t&&t.nodes,!0),this.edgesHandler.setData(t&&t.edges,!0),this.body.emitter.emit("_dataChanged"),this.body.emitter.emit("_dataLoaded"),this.body.emitter.emit("initPhysics")},o.prototype.destroy=function(){this.body.emitter.emit("destroy"),this.body.emitter.off(),this.off(),delete this.groups,delete this.canvas,delete this.selectionHandler,delete this.interactionHandler,delete this.view,delete this.renderer,delete this.physics,delete this.layoutEngine,delete this.clustering,delete this.manipulation,delete this.nodesHandler,delete this.edgesHandler,delete this.configurator,delete this.images;for(var t in this.body.nodes)this.body.nodes.hasOwnProperty(t)&&delete this.body.nodes[t];for(var e in this.body.edges)this.body.edges.hasOwnProperty(e)&&delete this.body.edges[e];s.recursiveDOMDelete(this.body.container)},o.prototype._updateValueRange=function(t){var e,i=void 0,o=void 0,n=0;for(e in t)if(t.hasOwnProperty(e)){var s=t[e].getValue();void 0!==s&&(i=void 0===i?s:Math.min(s,i),o=void 0===o?s:Math.max(s,o),n+=s)}if(void 0!==i&&void 0!==o)for(e in t)t.hasOwnProperty(e)&&t[e].setValueRange(i,o,n)},o.prototype.isActive=function(){return!this.activator||this.activator.active},o.prototype.setSize=function(){return this.canvas.setSize.apply(this.canvas,arguments)},o.prototype.canvasToDOM=function(){return this.canvas.canvasToDOM.apply(this.canvas,arguments)},o.prototype.DOMtoCanvas=function(){return this.canvas.DOMtoCanvas.apply(this.canvas,arguments)},o.prototype.findNode=function(){return this.clustering.findNode.apply(this.clustering,arguments)},o.prototype.isCluster=function(){return this.clustering.isCluster.apply(this.clustering,arguments)},o.prototype.openCluster=function(){return this.clustering.openCluster.apply(this.clustering,arguments)},o.prototype.cluster=function(){return this.clustering.cluster.apply(this.clustering,arguments)},o.prototype.getNodesInCluster=function(){return this.clustering.getNodesInCluster.apply(this.clustering,arguments)},o.prototype.clusterByConnection=function(){return this.clustering.clusterByConnection.apply(this.clustering,arguments)},o.prototype.clusterByHubsize=function(){return this.clustering.clusterByHubsize.apply(this.clustering,arguments)},o.prototype.clusterOutliers=function(){return this.clustering.clusterOutliers.apply(this.clustering,arguments)},o.prototype.getSeed=function(){return this.layoutEngine.getSeed.apply(this.layoutEngine,arguments)},o.prototype.enableEditMode=function(){return this.manipulation.enableEditMode.apply(this.manipulation,arguments)},o.prototype.disableEditMode=function(){return this.manipulation.disableEditMode.apply(this.manipulation,arguments)},o.prototype.addNodeMode=function(){return this.manipulation.addNodeMode.apply(this.manipulation,arguments)},o.prototype.editNode=function(){return this.manipulation.editNode.apply(this.manipulation,arguments)},o.prototype.editNodeMode=function(){return console.log("Deprecated: Please use editNode instead of editNodeMode."),this.manipulation.editNode.apply(this.manipulation,arguments)},o.prototype.addEdgeMode=function(){return this.manipulation.addEdgeMode.apply(this.manipulation,arguments)},o.prototype.editEdgeMode=function(){return this.manipulation.editEdgeMode.apply(this.manipulation,arguments)},o.prototype.deleteSelected=function(){return this.manipulation.deleteSelected.apply(this.manipulation,arguments)},o.prototype.getPositions=function(){return this.nodesHandler.getPositions.apply(this.nodesHandler,arguments)},o.prototype.storePositions=function(){return this.nodesHandler.storePositions.apply(this.nodesHandler,arguments)},o.prototype.moveNode=function(){return this.nodesHandler.moveNode.apply(this.nodesHandler,arguments)},o.prototype.getBoundingBox=function(){return this.nodesHandler.getBoundingBox.apply(this.nodesHandler,arguments)},o.prototype.getConnectedNodes=function(t){return void 0!==this.body.nodes[t]?this.nodesHandler.getConnectedNodes.apply(this.nodesHandler,arguments):this.edgesHandler.getConnectedNodes.apply(this.edgesHandler,arguments)},o.prototype.getConnectedEdges=function(){return this.nodesHandler.getConnectedEdges.apply(this.nodesHandler,arguments)},o.prototype.startSimulation=function(){return this.physics.startSimulation.apply(this.physics,arguments)},o.prototype.stopSimulation=function(){return this.physics.stopSimulation.apply(this.physics,arguments)},o.prototype.stabilize=function(){return this.physics.stabilize.apply(this.physics,arguments)},o.prototype.getSelection=function(){return this.selectionHandler.getSelection.apply(this.selectionHandler,arguments)},o.prototype.setSelection=function(){return this.selectionHandler.setSelection.apply(this.selectionHandler,arguments)},o.prototype.getSelectedNodes=function(){return this.selectionHandler.getSelectedNodes.apply(this.selectionHandler,arguments)},o.prototype.getSelectedEdges=function(){return this.selectionHandler.getSelectedEdges.apply(this.selectionHandler,arguments)},o.prototype.getNodeAt=function(){var t=this.selectionHandler.getNodeAt.apply(this.selectionHandler,arguments);return void 0!==t&&void 0!==t.id?t.id:t},o.prototype.getEdgeAt=function(){var t=this.selectionHandler.getEdgeAt.apply(this.selectionHandler,arguments);return void 0!==t&&void 0!==t.id?t.id:t},o.prototype.selectNodes=function(){return this.selectionHandler.selectNodes.apply(this.selectionHandler,arguments)},o.prototype.selectEdges=function(){return this.selectionHandler.selectEdges.apply(this.selectionHandler,arguments)},o.prototype.unselectAll=function(){this.selectionHandler.unselectAll.apply(this.selectionHandler,arguments),this.redraw()},o.prototype.redraw=function(){return this.renderer.redraw.apply(this.renderer,arguments)},o.prototype.getScale=function(){return this.view.getScale.apply(this.view,arguments)},o.prototype.getViewPosition=function(){return this.view.getViewPosition.apply(this.view,arguments)},o.prototype.fit=function(){return this.view.fit.apply(this.view,arguments)},o.prototype.moveTo=function(){return this.view.moveTo.apply(this.view,arguments)},o.prototype.focus=function(){return this.view.focus.apply(this.view,arguments)},o.prototype.releaseNode=function(){return this.view.releaseNode.apply(this.view,arguments)},o.prototype.getOptionsFromConfigurator=function(){var t={};return this.configurator&&(t=this.configurator.getOptions.apply(this.configurator)),t},t.exports=o},function(t,e,i){"undefined"!=typeof CanvasRenderingContext2D&&(CanvasRenderingContext2D.prototype.circle=function(t,e,i){this.beginPath(),this.arc(t,e,i,0,2*Math.PI,!1),this.closePath()},CanvasRenderingContext2D.prototype.square=function(t,e,i){this.beginPath(),this.rect(t-i,e-i,2*i,2*i),this.closePath()},CanvasRenderingContext2D.prototype.triangle=function(t,e,i){this.beginPath(),i*=1.15,e+=.275*i;var o=2*i,n=o/2,s=Math.sqrt(3)/6*o,r=Math.sqrt(o*o-n*n);this.moveTo(t,e-(r-s)),this.lineTo(t+n,e+s),this.lineTo(t-n,e+s),this.lineTo(t,e-(r-s)),this.closePath()},CanvasRenderingContext2D.prototype.triangleDown=function(t,e,i){this.beginPath(),i*=1.15,e-=.275*i;var o=2*i,n=o/2,s=Math.sqrt(3)/6*o,r=Math.sqrt(o*o-n*n);this.moveTo(t,e+(r-s)),this.lineTo(t+n,e-s),this.lineTo(t-n,e-s),this.lineTo(t,e+(r-s)),this.closePath()},CanvasRenderingContext2D.prototype.star=function(t,e,i){this.beginPath(),i*=.82,e+=.1*i;for(var o=0;o<10;o++){var n=o%2==0?1.3*i:.5*i;this.lineTo(t+n*Math.sin(2*o*Math.PI/10),e-n*Math.cos(2*o*Math.PI/10))}this.closePath()},CanvasRenderingContext2D.prototype.diamond=function(t,e,i){this.beginPath(),this.lineTo(t,e+i),this.lineTo(t+i,e),this.lineTo(t,e-i),this.lineTo(t-i,e),this.closePath()},CanvasRenderingContext2D.prototype.roundRect=function(t,e,i,o,n){var s=Math.PI/180;i-2*n<0&&(n=i/2),o-2*n<0&&(n=o/2),this.beginPath(),this.moveTo(t+n,e),this.lineTo(t+i-n,e),this.arc(t+i-n,e+n,n,270*s,360*s,!1),this.lineTo(t+i,e+o-n),this.arc(t+i-n,e+o-n,n,0,90*s,!1),this.lineTo(t+n,e+o),this.arc(t+n,e+o-n,n,90*s,180*s,!1),this.lineTo(t,e+n),this.arc(t+n,e+n,n,180*s,270*s,!1),this.closePath()},CanvasRenderingContext2D.prototype.ellipse_vis=function(t,e,i,o){var n=i/2*.5522848,s=o/2*.5522848,r=t+i,a=e+o,h=t+i/2,d=e+o/2;this.beginPath(),this.moveTo(t,d),this.bezierCurveTo(t,d-s,h-n,e,h,e),this.bezierCurveTo(h+n,e,r,d-s,r,d),this.bezierCurveTo(r,d+s,h+n,a,h,a),this.bezierCurveTo(h-n,a,t,d+s,t,d),this.closePath()},CanvasRenderingContext2D.prototype.database=function(t,e,i,o){var n=i,s=o*(1/3),r=n/2*.5522848,a=s/2*.5522848,h=t+n,d=e+s,l=t+n/2,u=e+s/2,c=e+(o-s/2),p=e+o;this.beginPath(),this.moveTo(h,u),this.bezierCurveTo(h,u+a,l+r,d,l,d),this.bezierCurveTo(l-r,d,t,u+a,t,u),this.bezierCurveTo(t,u-a,l-r,e,l,e),this.bezierCurveTo(l+r,e,h,u-a,h,u),this.lineTo(h,c),this.bezierCurveTo(h,c+a,l+r,p,l,p),this.bezierCurveTo(l-r,p,t,c+a,t,c),this.lineTo(t,u)},CanvasRenderingContext2D.prototype.dashedLine=function(t,e,i,o,n){this.beginPath(),this.moveTo(t,e);for(var s=n.length,r=i-t,a=o-e,h=a/r,d=Math.sqrt(r*r+a*a),l=0,u=!0,c=0,p=n[0];d>=.1;)p=n[l++%s],p>d&&(p=d),c=Math.sqrt(p*p/(1+h*h)),c=r<0?-c:c,t+=c,e+=h*c,!0===u?this.lineTo(t,e):this.moveTo(t,e),d-=p,u=!u},CanvasRenderingContext2D.prototype.hexagon=function(t,e,i){this.beginPath();var o=2*Math.PI/6;this.moveTo(t+i,e);for(var n=1;n<6;n++)this.lineTo(t+i*Math.cos(o*n),e+i*Math.sin(o*n));this.closePath()})},function(t,e,i){e.en={edit:"Edit",del:"Delete selected",back:"Back",addNode:"Add Node",addEdge:"Add Edge",editNode:"Edit Node",editEdge:"Edit Edge",addDescription:"Click in an empty space to place a new node.",edgeDescription:"Click on a node and drag the edge to another node to connect them.",editEdgeDescription:"Click on the control points and drag them to a node to connect to it.",createEdgeError:"Cannot link edges to a cluster.",deleteClusterError:"Clusters cannot be deleted.",editClusterError:"Clusters cannot be edited."},e.en_EN=e.en,e.en_US=e.en,e.de={edit:"Editieren",del:"Lösche Auswahl",back:"Zurück",addNode:"Knoten hinzufügen",addEdge:"Kante hinzufügen",editNode:"Knoten editieren",editEdge:"Kante editieren",addDescription:"Klicke auf eine freie Stelle, um einen neuen Knoten zu plazieren.",edgeDescription:"Klicke auf einen Knoten und ziehe die Kante zu einem anderen Knoten, um diese zu verbinden.",editEdgeDescription:"Klicke auf die Verbindungspunkte und ziehe diese auf einen Knoten, um sie zu verbinden.",createEdgeError:"Es ist nicht möglich, Kanten mit Clustern zu verbinden.",deleteClusterError:"Cluster können nicht gelöscht werden.",editClusterError:"Cluster können nicht editiert werden."},e.de_DE=e.de,e.es={edit:"Editar",del:"Eliminar selección",back:"Ãtras",addNode:"Añadir nodo",addEdge:"Añadir arista",editNode:"Editar nodo",editEdge:"Editar arista",addDescription:"Haga clic en un lugar vacío para colocar un nuevo nodo.",edgeDescription:"Haga clic en un nodo y arrastre la arista hacia otro nodo para conectarlos.",editEdgeDescription:"Haga clic en un punto de control y arrastrelo a un nodo para conectarlo.",createEdgeError:"No se puede conectar una arista a un grupo.",deleteClusterError:"No es posible eliminar grupos.",editClusterError:"No es posible editar grupos."},e.es_ES=e.es,e.it={edit:"Modifica",del:"Cancella la selezione",back:"Indietro",addNode:"Aggiungi un nodo",addEdge:"Aggiungi un vertice",editNode:"Modifica il nodo",editEdge:"Modifica il vertice",addDescription:"Clicca per aggiungere un nuovo nodo",edgeDescription:"Clicca su un nodo e trascinalo ad un altro nodo per connetterli.",editEdgeDescription:"Clicca sui Punti di controllo e trascinali ad un nodo per connetterli.",createEdgeError:"Non si possono collegare vertici ad un cluster",deleteClusterError:"I cluster non possono essere cancellati",editClusterError:"I clusters non possono essere modificati."},e.it_IT=e.it,e.nl={edit:"Wijzigen",del:"Selectie verwijderen",back:"Terug",addNode:"Node toevoegen",addEdge:"Link toevoegen",editNode:"Node wijzigen",editEdge:"Link wijzigen",addDescription:"Klik op een leeg gebied om een nieuwe node te maken.",edgeDescription:"Klik op een node en sleep de link naar een andere node om ze te verbinden.",editEdgeDescription:"Klik op de verbindingspunten en sleep ze naar een node om daarmee te verbinden.",createEdgeError:"Kan geen link maken naar een cluster.",deleteClusterError:"Clusters kunnen niet worden verwijderd.",editClusterError:"Clusters kunnen niet worden aangepast."},e.nl_NL=e.nl,e.nl_BE=e.nl,e["pt-br"]={edit:"Editar",del:"Remover selecionado",back:"Voltar",addNode:"Adicionar nó",addEdge:"Adicionar aresta",editNode:"Editar nó",editEdge:"Editar aresta",addDescription:"Clique em um espaço em branco para adicionar um novo nó",edgeDescription:"Clique em um nó e arraste a aresta até outro nó para conectá-los",editEdgeDescription:"Clique nos pontos de controle e os arraste para um nó para conectá-los",createEdgeError:"Não foi possível linkar arestas a um cluster.",deleteClusterError:"Clusters não puderam ser removidos.",editClusterError:"Clusters não puderam ser editados."},e["pt-BR"]=e["pt-br"],e.pt_BR=e["pt-br"],e.pt_br=e["pt-br"],e.ru={edit:"Редактировать",del:"Удалить выбранное",back:"Ðазад",addNode:"Добавить узел",addEdge:"Добавить ребро",editNode:"Редактировать узел",editEdge:"Редактировать ребро",addDescription:"Кликните в Ñвободное меÑто, чтобы добавить новый узел.",edgeDescription:"Кликните на узел и протÑните ребро к другому узлу, чтобы Ñоединить их.",editEdgeDescription:"Кликните на контрольные точки и перетащите их в узел, чтобы подключитьÑÑ Ðº нему.",createEdgeError:"Ðевозможно Ñоединить ребра в клаÑтер.",deleteClusterError:"КлаÑтеры не могут быть удалены",editClusterError:"КлаÑтеры недоÑтупны Ð´Ð»Ñ Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ."},e.ru_RU=e.ru,e.cn={edit:"编辑",del:"删除选定",back:"返回",addNode:"添加节点",addEdge:"添加连接线",editNode:"编辑节点",editEdge:"编辑连接线",addDescription:"å•击空白处放置新节点。",edgeDescription:"å•击æŸä¸ªèŠ‚ç‚¹å¹¶å°†è¯¥è¿žæŽ¥çº¿æ‹–åŠ¨åˆ°å¦ä¸€ä¸ªèŠ‚ç‚¹ä»¥è¿žæŽ¥å®ƒä»¬ã€‚",editEdgeDescription:"å•击控制节点并将它们拖到节点上连接。",createEdgeError:"无法将连接线连接到群集。",deleteClusterError:"无法删除群集。",editClusterError:"无法编辑群集。"},e.zh_CN=e.cn},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(0),s=o(n),r=i(1),a=o(r),h=function(){function t(){(0,s.default)(this,t),this.NUM_ITERATIONS=4,this.image=new Image,this.canvas=document.createElement("canvas")}return(0,a.default)(t,[{key:"init",value:function(){if(!this.initialized()){this.src=this.image.src;var t=this.image.width,e=this.image.height;this.width=t,this.height=e;var i=Math.floor(e/2),o=Math.floor(e/4),n=Math.floor(e/8),s=Math.floor(e/16),r=Math.floor(t/2),a=Math.floor(t/4),h=Math.floor(t/8),d=Math.floor(t/16);this.canvas.width=3*a,this.canvas.height=i,this.coordinates=[[0,0,r,i],[r,0,a,o],[r,o,h,n],[5*h,o,d,s]],this._fillMipMap()}}},{key:"initialized",value:function(){return void 0!==this.coordinates}},{key:"_fillMipMap",value:function(){var t=this.canvas.getContext("2d"),e=this.coordinates[0];t.drawImage(this.image,e[0],e[1],e[2],e[3]);for(var i=1;i2){e*=.5;for(var r=0;e>2&&r=this.NUM_ITERATIONS&&(r=this.NUM_ITERATIONS-1);var a=this.coordinates[r];t.drawImage(this.canvas,a[0],a[1],a[2],a[3],i,o,n,s)}else t.drawImage(this.image,i,o,n,s)}}]),t}();e.default=h},function(t,e,i){ function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(0),s=o(n),r=i(1),a=o(r),h=i(2),d=function(){function t(){(0,s.default)(this,t),this.clear(),this.defaultIndex=0,this.groupsArray=[],this.groupIndex=0,this.defaultGroups=[{border:"#2B7CE9",background:"#97C2FC",highlight:{border:"#2B7CE9",background:"#D2E5FF"},hover:{border:"#2B7CE9",background:"#D2E5FF"}},{border:"#FFA500",background:"#FFFF00",highlight:{border:"#FFA500",background:"#FFFFA3"},hover:{border:"#FFA500",background:"#FFFFA3"}},{border:"#FA0A10",background:"#FB7E81",highlight:{border:"#FA0A10",background:"#FFAFB1"},hover:{border:"#FA0A10",background:"#FFAFB1"}},{border:"#41A906",background:"#7BE141",highlight:{border:"#41A906",background:"#A1EC76"},hover:{border:"#41A906",background:"#A1EC76"}},{border:"#E129F0",background:"#EB7DF4",highlight:{border:"#E129F0",background:"#F0B3F5"},hover:{border:"#E129F0",background:"#F0B3F5"}},{border:"#7C29F0",background:"#AD85E4",highlight:{border:"#7C29F0",background:"#D3BDF0"},hover:{border:"#7C29F0",background:"#D3BDF0"}},{border:"#C37F00",background:"#FFA807",highlight:{border:"#C37F00",background:"#FFCA66"},hover:{border:"#C37F00",background:"#FFCA66"}},{border:"#4220FB",background:"#6E6EFD",highlight:{border:"#4220FB",background:"#9B9BFD"},hover:{border:"#4220FB",background:"#9B9BFD"}},{border:"#FD5A77",background:"#FFC0CB",highlight:{border:"#FD5A77",background:"#FFD1D9"},hover:{border:"#FD5A77",background:"#FFD1D9"}},{border:"#4AD63A",background:"#C2FABC",highlight:{border:"#4AD63A",background:"#E6FFE3"},hover:{border:"#4AD63A",background:"#E6FFE3"}},{border:"#990000",background:"#EE0000",highlight:{border:"#BB0000",background:"#FF3333"},hover:{border:"#BB0000",background:"#FF3333"}},{border:"#FF6000",background:"#FF6000",highlight:{border:"#FF6000",background:"#FF6000"},hover:{border:"#FF6000",background:"#FF6000"}},{border:"#97C2FC",background:"#2B7CE9",highlight:{border:"#D2E5FF",background:"#2B7CE9"},hover:{border:"#D2E5FF",background:"#2B7CE9"}},{border:"#399605",background:"#255C03",highlight:{border:"#399605",background:"#255C03"},hover:{border:"#399605",background:"#255C03"}},{border:"#B70054",background:"#FF007E",highlight:{border:"#B70054",background:"#FF007E"},hover:{border:"#B70054",background:"#FF007E"}},{border:"#AD85E4",background:"#7C29F0",highlight:{border:"#D3BDF0",background:"#7C29F0"},hover:{border:"#D3BDF0",background:"#7C29F0"}},{border:"#4557FA",background:"#000EA1",highlight:{border:"#6E6EFD",background:"#000EA1"},hover:{border:"#6E6EFD",background:"#000EA1"}},{border:"#FFC0CB",background:"#FD5A77",highlight:{border:"#FFD1D9",background:"#FD5A77"},hover:{border:"#FFD1D9",background:"#FD5A77"}},{border:"#C2FABC",background:"#74D66A",highlight:{border:"#E6FFE3",background:"#74D66A"},hover:{border:"#E6FFE3",background:"#74D66A"}},{border:"#EE0000",background:"#990000",highlight:{border:"#FF3333",background:"#BB0000"},hover:{border:"#FF3333",background:"#BB0000"}}],this.options={},this.defaultOptions={useDefaultGroups:!0},h.extend(this.options,this.defaultOptions)}return(0,a.default)(t,[{key:"setOptions",value:function(t){var e=["useDefaultGroups"];if(void 0!==t)for(var i in t)if(t.hasOwnProperty(i)&&-1===e.indexOf(i)){var o=t[i];this.add(i,o)}}},{key:"clear",value:function(){this.groups={},this.groupsArray=[]}},{key:"get",value:function(t){var e=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],i=this.groups[t];if(void 0===i&&e)if(!1===this.options.useDefaultGroups&&this.groupsArray.length>0){var o=this.groupIndex%this.groupsArray.length;this.groupIndex++,i={},i.color=this.groups[this.groupsArray[o]],this.groups[t]=i}else{var n=this.defaultIndex%this.defaultGroups.length;this.defaultIndex++,i={},i.color=this.defaultGroups[n],this.groups[t]=i}return i}},{key:"add",value:function(t,e){return this.groups[t]=e,this.groupsArray.push(t),e}}]),t}();e.default=d},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(0),s=o(n),r=i(1),a=o(r),h=i(2),d=i(11),l=i(12),u=i(47).default,c=function(){function t(e,i,o,n){var r=this;if((0,s.default)(this,t),this.body=e,this.images=i,this.groups=o,this.layoutEngine=n,this.body.functions.createNode=this.create.bind(this),this.nodesListeners={add:function(t,e){r.add(e.items)},update:function(t,e){r.update(e.items,e.data,e.oldData)},remove:function(t,e){r.remove(e.items)}},this.defaultOptions={borderWidth:1,borderWidthSelected:2,brokenImage:void 0,color:{border:"#2B7CE9",background:"#97C2FC",highlight:{border:"#2B7CE9",background:"#D2E5FF"},hover:{border:"#2B7CE9",background:"#D2E5FF"}},fixed:{x:!1,y:!1},font:{color:"#343434",size:14,face:"arial",background:"none",strokeWidth:0,strokeColor:"#ffffff",align:"center",vadjust:0,multi:!1,bold:{mod:"bold"},boldital:{mod:"bold italic"},ital:{mod:"italic"},mono:{mod:"",size:15,face:"monospace",vadjust:2}},group:void 0,hidden:!1,icon:{face:"FontAwesome",code:void 0,size:50,color:"#2B7CE9"},image:void 0,label:void 0,labelHighlightBold:!0,level:void 0,margin:{top:5,right:5,bottom:5,left:5},mass:1,physics:!0,scaling:{min:10,max:30,label:{enabled:!1,min:14,max:30,maxVisible:30,drawThreshold:5},customScalingFunction:function(t,e,i,o){if(e===t)return.5;var n=1/(e-t);return Math.max(0,(o-t)*n)}},shadow:{enabled:!1,color:"rgba(0,0,0,0.5)",size:10,x:5,y:5},shape:"ellipse",shapeProperties:{borderDashes:!1,borderRadius:6,interpolation:!0,useImageSize:!1,useBorderWithImage:!1},size:25,title:void 0,value:void 0,x:void 0,y:void 0},this.defaultOptions.mass<=0)throw"Internal error: mass in defaultOptions of NodesHandler may not be zero or negative";this.options=h.bridgeObject(this.defaultOptions),this.bindEventListeners()}return(0,a.default)(t,[{key:"bindEventListeners",value:function(){var t=this;this.body.emitter.on("refreshNodes",this.refresh.bind(this)),this.body.emitter.on("refresh",this.refresh.bind(this)),this.body.emitter.on("destroy",function(){h.forEach(t.nodesListeners,function(e,i){t.body.data.nodes&&t.body.data.nodes.off(i,e)}),delete t.body.functions.createNode,delete t.nodesListeners.add,delete t.nodesListeners.update,delete t.nodesListeners.remove,delete t.nodesListeners})}},{key:"setOptions",value:function(t){if(void 0!==t){if(u.parseOptions(this.options,t),void 0!==t.shape)for(var e in this.body.nodes)this.body.nodes.hasOwnProperty(e)&&this.body.nodes[e].updateShape();if(void 0!==t.font)for(var i in this.body.nodes)this.body.nodes.hasOwnProperty(i)&&(this.body.nodes[i].updateLabelModule(),this.body.nodes[i].needsRefresh());if(void 0!==t.size)for(var o in this.body.nodes)this.body.nodes.hasOwnProperty(o)&&this.body.nodes[o].needsRefresh();void 0===t.hidden&&void 0===t.physics||this.body.emitter.emit("_dataChanged")}}},{key:"setData",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]&&arguments[1],i=this.body.data.nodes;if(t instanceof d||t instanceof l)this.body.data.nodes=t;else if(Array.isArray(t))this.body.data.nodes=new d,this.body.data.nodes.add(t);else{if(t)throw new TypeError("Array or DataSet expected");this.body.data.nodes=new d}if(i&&h.forEach(this.nodesListeners,function(t,e){i.off(e,t)}),this.body.nodes={},this.body.data.nodes){var o=this;h.forEach(this.nodesListeners,function(t,e){o.body.data.nodes.on(e,t)});var n=this.body.data.nodes.getIds();this.add(n,!0)}!1===e&&this.body.emitter.emit("_dataChanged")}},{key:"add",value:function(t){for(var e=arguments.length>1&&void 0!==arguments[1]&&arguments[1],i=void 0,o=[],n=0;n1&&void 0!==arguments[1]?arguments[1]:u)(t,this.body,this.images,this.groups,this.options,this.defaultOptions)}},{key:"refresh",value:function(){var t=this,e=arguments.length>0&&void 0!==arguments[0]&&arguments[0];h.forEach(this.body.nodes,function(i,o){var n=t.body.data.nodes.get(o);void 0!==n&&(!0===e&&i.setOptions({x:null,y:null}),i.setOptions({fixed:!1}),i.setOptions(n))})}},{key:"getPositions",value:function(t){var e={};if(void 0!==t){if(!0===Array.isArray(t)){for(var i=0;i0)for(var r=0;r0)for(var p=0;p0&&void 0!==arguments[0]&&arguments[0];this.spacing&&(this.add(" "),this.spacing=!1),this.buffer.length>0&&(e.push({text:this.buffer,mod:this.modName()}),this.buffer="")},i.add=function(t){" "===t&&(i.spacing=!0),i.spacing&&(this.buffer+=" ",this.spacing=!1)," "!=t&&(this.buffer+=t)};i.position/.test(t.substr(i.position,3))?i.mono||i.ital||!//.test(t.substr(i.position,3))?!i.mono&&//.test(t.substr(i.position,6))?(i.emitBlock(),i.mono=!0,i.modStack.unshift("mono"),i.position+=5):!i.mono&&"bold"===i.mod()&&/<\/b>/.test(t.substr(i.position,4))?(i.emitBlock(),i.bold=!1,i.modStack.shift(),i.position+=3):!i.mono&&"ital"===i.mod()&&/<\/i>/.test(t.substr(i.position,4))?(i.emitBlock(),i.ital=!1,i.modStack.shift(),i.position+=3):"mono"===i.mod()&&/<\/code>/.test(t.substr(i.position,7))?(i.emitBlock(),i.mono=!1,i.modStack.shift(),i.position+=6):i.add(o):(i.emitBlock(),i.ital=!0,i.modStack.unshift("ital"),i.position+=2):(i.emitBlock(),i.bold=!0,i.modStack.unshift("bold"),i.position+=2):/&/.test(o)?/</.test(t.substr(i.position,4))?(i.add("<"),i.position+=3):/&/.test(t.substr(i.position,5))?(i.add("&"),i.position+=4):i.add("&"):i.add(o),i.position++}return i.emitBlock(),e}},{key:"splitMarkdownBlocks",value:function(t){var e=[],i={bold:!1,ital:!1,mono:!1,beginable:!0,spacing:!1,position:0,buffer:"",modStack:[]};for(i.mod=function(){return 0===this.modStack.length?"normal":this.modStack[0]},i.modName=function(){return 0===this.modStack.length?"normal":"mono"===this.modStack[0]?"mono":i.bold&&i.ital?"boldital":i.bold?"bold":i.ital?"ital":void 0},i.emitBlock=function(){arguments.length>0&&void 0!==arguments[0]&&arguments[0];this.spacing&&(this.add(" "),this.spacing=!1),this.buffer.length>0&&(e.push({text:this.buffer,mod:this.modName()}),this.buffer="")},i.add=function(t){" "===t&&(i.spacing=!0),i.spacing&&(this.buffer+=" ",this.spacing=!1)," "!=t&&(this.buffer+=t)};i.positionthis.parent.fontOptions.maxWdt}},{key:"getLongestFit",value:function(t){for(var e="",i=0;i1&&void 0!==arguments[1]?arguments[1]:"normal",i=arguments.length>2&&void 0!==arguments[2]&&arguments[2];t=t.replace(/^( +)/g,"$1\r"),t=t.replace(/([^\r][^ ]*)( +)/g,"$1\r$2\r");for(var o=t.split("\r");o.length>0;){var n=this.getLongestFit(o);if(0===n){var s=o[0],r=this.getLongestFitWord(s);this.lines.newLine(s.slice(0,r),e),o[0]=s.slice(r)}else{var a=n;" "===o[n-1]?n--:" "===o[a]&&a++;var h=o.slice(0,n).join("");n==o.length&&i?this.lines.append(h,e):this.lines.newLine(h,e),o=o.slice(a)}}}}]),t}();e.default=l},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(90),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=function(){function t(e){(0,a.default)(this,t),this.measureText=e,this.current=0,this.width=0,this.height=0,this.lines=[]}return(0,d.default)(t,[{key:"_add",value:function(t,e){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"normal";void 0===this.lines[t]&&(this.lines[t]={width:0,height:0,blocks:[]});var o=e;void 0!==e&&""!==e||(o=" ");var n=this.measureText(o,i),r=(0,s.default)({},n.values);r.text=e,r.width=n.width,r.mod=i,void 0!==e&&""!==e||(r.width=0),this.lines[t].blocks.push(r),this.lines[t].width+=r.width}},{key:"curWidth",value:function(){var t=this.lines[this.current];return void 0===t?0:t.width}},{key:"append",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"normal";this._add(this.current,t,e)}},{key:"newLine",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"normal";this._add(this.current,t,e),this.current++}},{key:"determineLineHeights",value:function(){for(var t=0;tt&&(t=o.width),e+=o.height}this.width=t,this.height=e}},{key:"removeEmptyBlocks",value:function(){for(var t=[],e=0;e1&&void 0!==arguments[1]?arguments[1]:this.selected,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:this.hover;if(this.needsRefresh(e,i)){var o=this.getDimensionsFromLabel(t,e,i);this.width=o.width+this.margin.right+this.margin.left,this.height=o.height+this.margin.top+this.margin.bottom,this.radius=this.width/2}}},{key:"draw",value:function(t,e,i,o,n,s){this.resize(t,o,n),this.left=e-this.width/2,this.top=i-this.height/2,this.initContextForDraw(t,s),t.roundRect(this.left,this.top,this.width,this.height,s.borderRadius),this.performFill(t,s),this.updateBoundingBox(e,i,t,o,n),this.labelModule.draw(t,this.left+this.textSize.width/2+this.margin.left,this.top+this.textSize.height/2+this.margin.top,o,n)}},{key:"updateBoundingBox",value:function(t,e,i,o,n){this._updateBoundingBox(t,e,i,o,n);var s=this.options.shapeProperties.borderRadius;this._addBoundingBoxMargin(s)}},{key:"distanceToBorder",value:function(t,e){this.resize(t);var i=this.options.borderWidth;return Math.min(Math.abs(this.width/2/Math.cos(e)),Math.abs(this.height/2/Math.sin(e)))+i}}]),e}(m.default);e.default=v},function(t,e,i){i(195),t.exports=i(7).Object.getPrototypeOf},function(t,e,i){var o=i(41),n=i(85);i(87)("getPrototypeOf",function(){return function(t){return n(o(t))}})},function(t,e,i){t.exports={default:i(197),__esModule:!0}},function(t,e,i){i(198),t.exports=i(7).Object.setPrototypeOf},function(t,e,i){var o=i(17);o(o.S,"Object",{setPrototypeOf:i(199).set})},function(t,e,i){var o=i(32),n=i(27),s=function(t,e){if(n(t),!o(e)&&null!==e)throw TypeError(e+": can't set as prototype!")};t.exports={set:Object.setPrototypeOf||("__proto__"in{}?function(t,e,o){try{o=i(80)(Function.call,i(89).f(Object.prototype,"__proto__").set,2),o(t,[]),e=!(t instanceof Array)}catch(t){e=!0}return function(t,i){return s(t,i),e?t.__proto__=i:o(t,i),t}}({},!1):void 0),check:s}},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(73),m=o(f),v=function(t){function e(t,i,o){(0,a.default)(this,e);var n=(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o));return n._setMargins(o),n}return(0,p.default)(e,t),(0,d.default)(e,[{key:"resize",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.selected,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:this.hover;if(this.needsRefresh(e,i)){var o=this.getDimensionsFromLabel(t,e,i),n=Math.max(o.width+this.margin.right+this.margin.left,o.height+this.margin.top+this.margin.bottom);this.options.size=n/2,this.width=n,this.height=n,this.radius=this.width/2}}},{key:"draw",value:function(t,e,i,o,n,s){this.resize(t,o,n),this.left=e-this.width/2,this.top=i-this.height/2,this._drawRawCircle(t,e,i,s),this.updateBoundingBox(e,i),this.labelModule.draw(t,this.left+this.textSize.width/2+this.margin.left,i,o,n)}},{key:"updateBoundingBox",value:function(t,e){this.boundingBox.top=e-this.options.size,this.boundingBox.left=t-this.options.size,this.boundingBox.right=t+this.options.size,this.boundingBox.bottom=e+this.options.size}},{key:"distanceToBorder",value:function(t,e){return this.resize(t),.5*this.width}}]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(73),m=o(f),v=function(t){function e(t,i,o,n,r){(0,a.default)(this,e);var h=(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o));return h.setImages(n,r),h}return(0,p.default)(e,t),(0,d.default)(e,[{key:"resize",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.selected,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:this.hover;if(void 0===this.imageObj.src||void 0===this.imageObj.width||void 0===this.imageObj.height){var o=2*this.options.size;return this.width=o,this.height=o,void(this.radius=.5*this.width)}this.needsRefresh(e,i)&&this._resizeImage()}},{key:"draw",value:function(t,e,i,o,n,s){this.switchImages(o),this.resize(),this.left=e-this.width/2,this.top=i-this.height/2,this._drawRawCircle(t,e,i,s),t.save(),t.clip(),this._drawImageAtPosition(t,s),t.restore(),this._drawImageLabel(t,e,i,o,n),this.updateBoundingBox(e,i)}},{key:"updateBoundingBox",value:function(t,e){this.boundingBox.top=e-this.options.size,this.boundingBox.left=t-this.options.size,this.boundingBox.right=t+this.options.size,this.boundingBox.bottom=e+this.options.size,this.boundingBox.left=Math.min(this.boundingBox.left,this.labelModule.size.left),this.boundingBox.right=Math.max(this.boundingBox.right,this.labelModule.size.left+this.labelModule.size.width),this.boundingBox.bottom=Math.max(this.boundingBox.bottom,this.boundingBox.bottom+this.labelOffset)}},{key:"distanceToBorder",value:function(t,e){return this.resize(t),.5*this.width}}]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(23),m=o(f),v=function(t){function e(t,i,o){(0,a.default)(this,e);var n=(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o));return n._setMargins(o),n}return(0,p.default)(e,t),(0,d.default)(e,[{key:"resize",value:function(t,e,i){if(this.needsRefresh(e,i)){var o=this.getDimensionsFromLabel(t,e,i),n=o.width+this.margin.right+this.margin.left;this.width=n,this.height=n,this.radius=this.width/2}}},{key:"draw",value:function(t,e,i,o,n,s){this.resize(t,o,n),this.left=e-this.width/2,this.top=i-this.height/2,this.initContextForDraw(t,s),t.database(e-this.width/2,i-this.height/2,this.width,this.height),this.performFill(t,s),this.updateBoundingBox(e,i,t,o,n),this.labelModule.draw(t,this.left+this.textSize.width/2+this.margin.left,this.top+this.textSize.height/2+this.margin.top,o,n)}},{key:"distanceToBorder",value:function(t,e){return this._distanceToBorder(t,e)}}]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(24),m=o(f),v=function(t){function e(t,i,o){return(0,a.default)(this,e),(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o))}return(0,p.default)(e,t),(0,d.default)(e,[{key:"draw",value:function(t,e,i,o,n,s){this._drawShape(t,"diamond",4,e,i,o,n,s)}},{key:"distanceToBorder",value:function(t,e){return this._distanceToBorder(t,e)}}]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(24),m=o(f),v=function(t){function e(t,i,o){return(0,a.default)(this,e),(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o))}return(0,p.default)(e,t),(0,d.default)(e,[{key:"draw",value:function(t,e,i,o,n,s){this._drawShape(t,"circle",2,e,i,o,n,s)}},{key:"distanceToBorder",value:function(t,e){return this.resize(t),this.options.size}}]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(23),m=o(f),v=function(t){function e(t,i,o){return(0,a.default)(this,e),(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o))}return(0,p.default)(e,t),(0,d.default)(e,[{key:"resize",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.selected,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:this.hover;if(this.needsRefresh(e,i)){var o=this.getDimensionsFromLabel(t,e,i);this.height=2*o.height,this.width=o.width+o.height,this.radius=.5*this.width}}},{key:"draw",value:function(t,e,i,o,n,s){this.resize(t,o,n),this.left=e-.5*this.width,this.top=i-.5*this.height,this.initContextForDraw(t,s),t.ellipse_vis(this.left,this.top,this.width,this.height),this.performFill(t,s),this.updateBoundingBox(e,i,t,o,n),this.labelModule.draw(t,e,i,o,n)}},{key:"distanceToBorder",value:function(t,e){this.resize(t);var i=.5*this.width,o=.5*this.height,n=Math.sin(e)*i,s=Math.cos(e)*o;return i*o/Math.sqrt(n*n+s*s)}}]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(23),m=o(f),v=function(t){function e(t,i,o){(0,a.default)(this,e);var n=(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o));return n._setMargins(o),n}return(0,p.default)(e,t),(0,d.default)(e,[{key:"resize",value:function(t,e,i){this.needsRefresh(e,i)&&(this.iconSize={width:Number(this.options.icon.size),height:Number(this.options.icon.size)},this.width=this.iconSize.width+this.margin.right+this.margin.left,this.height=this.iconSize.height+this.margin.top+this.margin.bottom,this.radius=.5*this.width)}},{key:"draw",value:function(t,e,i,o,n,s){if(this.resize(t,o,n),this.options.icon.size=this.options.icon.size||50,this.left=e-this.width/2,this.top=i-this.height/2,this._icon(t,e,i,o,n,s),void 0!==this.options.label){this.labelModule.draw(t,this.left+this.iconSize.width/2+this.margin.left,i+this.height/2+5,o)}this.updateBoundingBox(e,i)}},{key:"updateBoundingBox",value:function(t,e){if(this.boundingBox.top=e-.5*this.options.icon.size,this.boundingBox.left=t-.5*this.options.icon.size,this.boundingBox.right=t+.5*this.options.icon.size,this.boundingBox.bottom=e+.5*this.options.icon.size,void 0!==this.options.label&&this.labelModule.size.width>0){this.boundingBox.left=Math.min(this.boundingBox.left,this.labelModule.size.left),this.boundingBox.right=Math.max(this.boundingBox.right,this.labelModule.size.left+this.labelModule.size.width),this.boundingBox.bottom=Math.max(this.boundingBox.bottom,this.boundingBox.bottom+this.labelModule.size.height+5)}}},{key:"_icon",value:function(t,e,i,o,n,s){var r=Number(this.options.icon.size);void 0!==this.options.icon.code?(t.font=(o?"bold ":"")+r+"px "+this.options.icon.face,t.fillStyle=this.options.icon.color||"black",t.textAlign="center",t.textBaseline="middle",this.enableShadow(t,s),t.fillText(this.options.icon.code,e,i),this.disableShadow(t,s)):console.error("When using the icon shape, you need to define the code in the icon options object. This can be done per node or globally.")}},{key:"distanceToBorder",value:function(t,e){return this._distanceToBorder(t,e)}}]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(73),m=o(f),v=function(t){function e(t,i,o,n,r){(0,a.default)(this,e);var h=(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o));return h.setImages(n,r),h}return(0,p.default)(e,t),(0,d.default)(e,[{key:"resize",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.selected,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:this.hover;if(void 0===this.imageObj.src||void 0===this.imageObj.width||void 0===this.imageObj.height){var o=2*this.options.size;return this.width=o,void(this.height=o)}this.needsRefresh(e,i)&&this._resizeImage()}},{key:"draw",value:function(t,e,i,o,n,s){if(this.switchImages(o),this.resize(),this.left=e-this.width/2,this.top=i-this.height/2,!0===this.options.shapeProperties.useBorderWithImage){var r=this.options.borderWidth,a=this.options.borderWidthSelected||2*this.options.borderWidth,h=(o?a:r)/this.body.view.scale;t.lineWidth=Math.min(this.width,h),t.beginPath(),t.strokeStyle=o?this.options.color.highlight.border:n?this.options.color.hover.border:this.options.color.border,t.fillStyle=o?this.options.color.highlight.background:n?this.options.color.hover.background:this.options.color.background,t.rect(this.left-.5*t.lineWidth,this.top-.5*t.lineWidth,this.width+t.lineWidth,this.height+t.lineWidth),t.fill(),this.performStroke(t,s),t.closePath()}this._drawImageAtPosition(t,s),this._drawImageLabel(t,e,i,o,n),this.updateBoundingBox(e,i)}},{key:"updateBoundingBox",value:function(t,e){this.resize(),this._updateBoundingBox(t,e),void 0!==this.options.label&&this.labelModule.size.width>0&&(this.boundingBox.left=Math.min(this.boundingBox.left,this.labelModule.size.left),this.boundingBox.right=Math.max(this.boundingBox.right,this.labelModule.size.left+this.labelModule.size.width),this.boundingBox.bottom=Math.max(this.boundingBox.bottom,this.boundingBox.bottom+this.labelOffset))}},{key:"distanceToBorder",value:function(t,e){return this._distanceToBorder(t,e)} }]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(24),m=o(f),v=function(t){function e(t,i,o){return(0,a.default)(this,e),(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o))}return(0,p.default)(e,t),(0,d.default)(e,[{key:"draw",value:function(t,e,i,o,n,s){this._drawShape(t,"square",2,e,i,o,n,s)}},{key:"distanceToBorder",value:function(t,e){return this._distanceToBorder(t,e)}}]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(24),m=o(f),v=function(t){function e(t,i,o){return(0,a.default)(this,e),(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o))}return(0,p.default)(e,t),(0,d.default)(e,[{key:"draw",value:function(t,e,i,o,n,s){this._drawShape(t,"hexagon",4,e,i,o,n,s)}},{key:"distanceToBorder",value:function(t,e){return this._distanceToBorder(t,e)}}]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(24),m=o(f),v=function(t){function e(t,i,o){return(0,a.default)(this,e),(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o))}return(0,p.default)(e,t),(0,d.default)(e,[{key:"draw",value:function(t,e,i,o,n,s){this._drawShape(t,"star",4,e,i,o,n,s)}},{key:"distanceToBorder",value:function(t,e){return this._distanceToBorder(t,e)}}]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(23),m=o(f),v=function(t){function e(t,i,o){(0,a.default)(this,e);var n=(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o));return n._setMargins(o),n}return(0,p.default)(e,t),(0,d.default)(e,[{key:"resize",value:function(t,e,i){this.needsRefresh(e,i)&&(this.textSize=this.labelModule.getTextSize(t,e,i),this.width=this.textSize.width+this.margin.right+this.margin.left,this.height=this.textSize.height+this.margin.top+this.margin.bottom,this.radius=.5*this.width)}},{key:"draw",value:function(t,e,i,o,n,s){this.resize(t,o,n),this.left=e-this.width/2,this.top=i-this.height/2,this.enableShadow(t,s),this.labelModule.draw(t,this.left+this.textSize.width/2+this.margin.left,this.top+this.textSize.height/2+this.margin.top,o,n),this.disableShadow(t,s),this.updateBoundingBox(e,i,t,o,n)}},{key:"distanceToBorder",value:function(t,e){return this._distanceToBorder(t,e)}}]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(24),m=o(f),v=function(t){function e(t,i,o){return(0,a.default)(this,e),(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o))}return(0,p.default)(e,t),(0,d.default)(e,[{key:"draw",value:function(t,e,i,o,n,s){this._drawShape(t,"triangle",3,e,i,o,n,s)}},{key:"distanceToBorder",value:function(t,e){return this._distanceToBorder(t,e)}}]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(24),m=o(f),v=function(t){function e(t,i,o){return(0,a.default)(this,e),(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o))}return(0,p.default)(e,t),(0,d.default)(e,[{key:"draw",value:function(t,e,i,o,n,s){this._drawShape(t,"triangleDown",3,e,i,o,n,s)}},{key:"distanceToBorder",value:function(t,e){return this._distanceToBorder(t,e)}}]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(0),s=o(n),r=i(1),a=o(r),h=i(2),d=i(11),l=i(12),u=i(74).default,c=function(){function t(e,i,o){var n=this;(0,s.default)(this,t),this.body=e,this.images=i,this.groups=o,this.body.functions.createEdge=this.create.bind(this),this.edgesListeners={add:function(t,e){n.add(e.items)},update:function(t,e){n.update(e.items)},remove:function(t,e){n.remove(e.items)}},this.options={},this.defaultOptions={arrows:{to:{enabled:!1,scaleFactor:1,type:"arrow"},middle:{enabled:!1,scaleFactor:1,type:"arrow"},from:{enabled:!1,scaleFactor:1,type:"arrow"}},arrowStrikethrough:!0,color:{color:"#848484",highlight:"#848484",hover:"#848484",inherit:"from",opacity:1},dashes:!1,font:{color:"#343434",size:14,face:"arial",background:"none",strokeWidth:2,strokeColor:"#ffffff",align:"horizontal",multi:!1,vadjust:0,bold:{mod:"bold"},boldital:{mod:"bold italic"},ital:{mod:"italic"},mono:{mod:"",size:15,face:"courier new",vadjust:2}},hidden:!1,hoverWidth:1.5,label:void 0,labelHighlightBold:!0,length:void 0,physics:!0,scaling:{min:1,max:15,label:{enabled:!0,min:14,max:30,maxVisible:30,drawThreshold:5},customScalingFunction:function(t,e,i,o){if(e===t)return.5;var n=1/(e-t);return Math.max(0,(o-t)*n)}},selectionWidth:1.5,selfReferenceSize:20,shadow:{enabled:!1,color:"rgba(0,0,0,0.5)",size:10,x:5,y:5},smooth:{enabled:!0,type:"dynamic",forceDirection:"none",roundness:.5},title:void 0,width:1,value:void 0},h.deepExtend(this.options,this.defaultOptions),this.bindEventListeners()}return(0,a.default)(t,[{key:"bindEventListeners",value:function(){var t=this;this.body.emitter.on("_forceDisableDynamicCurves",function(e){var i=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];"dynamic"===e&&(e="continuous");var o=!1;for(var n in t.body.edges)if(t.body.edges.hasOwnProperty(n)){var s=t.body.edges[n],r=t.body.data.edges._data[n];if(void 0!==r){var a=r.smooth;void 0!==a&&!0===a.enabled&&"dynamic"===a.type&&(void 0===e?s.setOptions({smooth:!1}):s.setOptions({smooth:{type:e}}),o=!0)}}!0===i&&!0===o&&t.body.emitter.emit("_dataChanged")}),this.body.emitter.on("_dataUpdated",function(){t.reconnectEdges()}),this.body.emitter.on("refreshEdges",this.refresh.bind(this)),this.body.emitter.on("refresh",this.refresh.bind(this)),this.body.emitter.on("destroy",function(){h.forEach(t.edgesListeners,function(e,i){t.body.data.edges&&t.body.data.edges.off(i,e)}),delete t.body.functions.createEdge,delete t.edgesListeners.add,delete t.edgesListeners.update,delete t.edgesListeners.remove,delete t.edgesListeners})}},{key:"setOptions",value:function(t){if(void 0!==t){u.parseOptions(this.options,t,!0,this.defaultOptions,!0);var e=!1;if(void 0!==t.smooth)for(var i in this.body.edges)this.body.edges.hasOwnProperty(i)&&(e=this.body.edges[i].updateEdgeType()||e);if(void 0!==t.font)for(var o in this.body.edges)this.body.edges.hasOwnProperty(o)&&this.body.edges[o].updateLabelModule();void 0===t.hidden&&void 0===t.physics&&!0!==e||this.body.emitter.emit("_dataChanged")}}},{key:"setData",value:function(t){var e=this,i=arguments.length>1&&void 0!==arguments[1]&&arguments[1],o=this.body.data.edges;if(t instanceof d||t instanceof l)this.body.data.edges=t;else if(Array.isArray(t))this.body.data.edges=new d,this.body.data.edges.add(t);else{if(t)throw new TypeError("Array or DataSet expected");this.body.data.edges=new d}if(o&&h.forEach(this.edgesListeners,function(t,e){o.off(e,t)}),this.body.edges={},this.body.data.edges){h.forEach(this.edgesListeners,function(t,i){e.body.data.edges.on(i,t)});var n=this.body.data.edges.getIds();this.add(n,!0)}this.body.emitter.emit("_adjustEdgesForHierarchicalLayout"),!1===i&&this.body.emitter.emit("_dataChanged")}},{key:"add",value:function(t){for(var e=arguments.length>1&&void 0!==arguments[1]&&arguments[1],i=this.body.edges,o=this.body.data.edges,n=0;n1&&void 0!==arguments[1])||arguments[1];if(0!==t.length){var i=this.body.edges;h.forEach(t,function(t){var e=i[t];void 0!==e&&e.remove()}),e&&this.body.emitter.emit("_dataChanged")}}},{key:"refresh",value:function(){var t=this;h.forEach(this.body.edges,function(e,i){var o=t.body.data.edges._data[i];void 0!==o&&e.setOptions(o)})}},{key:"create",value:function(t){return new u(t,this.body,this.options,this.defaultOptions)}},{key:"reconnectEdges",value:function(){var t,e=this.body.nodes,i=this.body.edges;for(t in e)e.hasOwnProperty(t)&&(e[t].edges=[]);for(t in i)if(i.hasOwnProperty(t)){var o=i[t];o.from=null,o.to=null,o.connect()}}},{key:"getConnectedNodes",value:function(t){var e=[];if(void 0!==this.body.edges[t]){var i=this.body.edges[t];void 0!==i.fromId&&e.push(i.fromId),void 0!==i.toId&&e.push(i.toId)}return e}},{key:"_updateState",value:function(){this._addMissingEdges(),this._removeInvalidEdges()}},{key:"_removeInvalidEdges",value:function(){var t=this,e=[];h.forEach(this.body.edges,function(i,o){var n=t.body.nodes[i.toId],s=t.body.nodes[i.fromId];void 0!==n&&!0===n.isCluster||void 0!==s&&!0===s.isCluster||void 0!==n&&void 0!==s||e.push(o)}),this.remove(e,!1)}},{key:"_addMissingEdges",value:function(){var t=this.body.edges,e=this.body.data.edges,i=[];e.forEach(function(e,o){void 0===t[o]&&i.push(o)}),this.add(i,!0)}}]),t}();e.default=c},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(30),s=o(n),r=i(3),a=o(r),h=i(0),d=o(h),l=i(1),u=o(l),c=i(4),p=o(c),f=i(5),m=o(f),v=i(216),g=o(v),y=function(t){function e(t,i,o){return(0,d.default)(this,e),(0,p.default)(this,(e.__proto__||(0,a.default)(e)).call(this,t,i,o))}return(0,m.default)(e,t),(0,u.default)(e,[{key:"_line",value:function(t,e,i){var o=i[0],n=i[1];this._bezierCurve(t,e,o,n)}},{key:"_getViaCoordinates",value:function(){var t=this.from.x-this.to.x,e=this.from.y-this.to.y,i=void 0,o=void 0,n=void 0,s=void 0,r=this.options.smooth.roundness;return(Math.abs(t)>Math.abs(e)||!0===this.options.smooth.forceDirection||"horizontal"===this.options.smooth.forceDirection)&&"vertical"!==this.options.smooth.forceDirection?(o=this.from.y,s=this.to.y,i=this.from.x-r*t,n=this.to.x+r*t):(o=this.from.y-r*e,s=this.to.y+r*e,i=this.from.x,n=this.to.x),[{x:i,y:o},{x:n,y:s}]}},{key:"getViaNode",value:function(){return this._getViaCoordinates()}},{key:"_findBorderPosition",value:function(t,e){return this._findBorderPositionBezier(t,e)}},{key:"_getDistanceToEdge",value:function(t,e,i,o,n,r){var a=arguments.length>6&&void 0!==arguments[6]?arguments[6]:this._getViaCoordinates(),h=(0,s.default)(a,2),d=h[0],l=h[1];return this._getDistanceToBezierEdge(t,e,i,o,n,r,d,l)}},{key:"getPoint",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this._getViaCoordinates(),i=(0,s.default)(e,2),o=i[0],n=i[1],r=t,a=[];return a[0]=Math.pow(1-r,3),a[1]=3*r*Math.pow(1-r,2),a[2]=3*Math.pow(r,2)*(1-r),a[3]=Math.pow(r,3),{x:a[0]*this.fromPoint.x+a[1]*o.x+a[2]*n.x+a[3]*this.toPoint.x,y:a[0]*this.fromPoint.y+a[1]*o.y+a[2]*n.y+a[3]*this.toPoint.y}}}]),e}(g.default);e.default=y},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(75),m=o(f),v=function(t){function e(t,i,o){return(0,a.default)(this,e),(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o))}return(0,p.default)(e,t),(0,d.default)(e,[{key:"_getDistanceToBezierEdge",value:function(t,e,i,o,n,s,r,a){var h=1e9,d=void 0,l=void 0,u=void 0,c=void 0,p=void 0,f=t,m=e,v=[0,0,0,0];for(l=1;l<10;l++)u=.1*l,v[0]=Math.pow(1-u,3),v[1]=3*u*Math.pow(1-u,2),v[2]=3*Math.pow(u,2)*(1-u),v[3]=Math.pow(u,3),c=v[0]*t+v[1]*r.x+v[2]*a.x+v[3]*i,p=v[0]*e+v[1]*r.y+v[2]*a.y+v[3]*o,l>0&&(d=this._getDistanceToLine(f,m,c,p,n,s),h=d1&&void 0!==arguments[1]?arguments[1]:this.via,i=t,o=void 0,n=void 0;if(this.from===this.to){var r=this._getCircleData(this.from),a=(0,s.default)(r,3),h=a[0],d=a[1],l=a[2],u=2*Math.PI*(1-i);o=h+l*Math.sin(u),n=d+l-l*(1-Math.cos(u))}else o=Math.pow(1-i,2)*this.fromPoint.x+2*i*(1-i)*e.x+Math.pow(i,2)*this.toPoint.x,n=Math.pow(1-i,2)*this.fromPoint.y+2*i*(1-i)*e.y+Math.pow(i,2)*this.toPoint.y;return{x:o,y:n}}},{key:"_findBorderPosition",value:function(t,e){return this._findBorderPositionBezier(t,e,this.via)}},{key:"_getDistanceToEdge",value:function(t,e,i,o,n,s){return this._getDistanceToBezierEdge(t,e,i,o,n,s,this.via)}}]),e}(g.default);e.default=y},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(75),m=o(f),v=function(t){function e(t,i,o){return(0,a.default)(this,e),(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o))}return(0,p.default)(e,t),(0,d.default)(e,[{key:"_line",value:function(t,e,i){this._bezierCurve(t,e,i)}},{key:"getViaNode",value:function(){return this._getViaCoordinates()}},{key:"_getViaCoordinates",value:function(){var t=void 0,e=void 0,i=this.options.smooth.roundness,o=this.options.smooth.type,n=Math.abs(this.from.x-this.to.x),s=Math.abs(this.from.y-this.to.y);if("discrete"===o||"diagonalCross"===o){var r=void 0,a=void 0;r=a=n<=s?i*s:i*n,this.from.x>this.to.x&&(r=-r),this.from.y>=this.to.y&&(a=-a),t=this.from.x+r,e=this.from.y+a,"discrete"===o&&(n<=s?t=nthis.to.x&&(_=-_),this.from.y>=this.to.y&&(w=-w),t=this.from.x+_,e=this.from.y+w,n<=s?t=this.from.x<=this.to.x?this.to.xt?this.to.x:t:e=this.from.y>=this.to.y?this.to.y>e?this.to.y:e:this.to.y2&&void 0!==arguments[2]?arguments[2]:{};return this._findBorderPositionBezier(t,e,i.via)}},{key:"_getDistanceToEdge",value:function(t,e,i,o,n,s){var r=arguments.length>6&&void 0!==arguments[6]?arguments[6]:this._getViaCoordinates();return this._getDistanceToBezierEdge(t,e,i,o,n,s,r)}},{key:"getPoint",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this._getViaCoordinates(),i=t;return{x:Math.pow(1-i,2)*this.fromPoint.x+2*i*(1-i)*e.x+Math.pow(i,2)*this.toPoint.x,y:Math.pow(1-i,2)*this.fromPoint.y+2*i*(1-i)*e.y+Math.pow(i,2)*this.toPoint.y}}}]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(118),m=o(f),v=function(t){function e(t,i,o){return(0,a.default)(this,e),(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o))}return(0,p.default)(e,t),(0,d.default)(e,[{key:"_line",value:function(t,e){t.beginPath(),t.moveTo(this.fromPoint.x,this.fromPoint.y),t.lineTo(this.toPoint.x,this.toPoint.y),this.enableShadow(t,e),t.stroke(),this.disableShadow(t,e)}},{key:"getViaNode",value:function(){}},{key:"getPoint",value:function(t){return{x:(1-t)*this.fromPoint.x+t*this.toPoint.x,y:(1-t)*this.fromPoint.y+t*this.toPoint.y}}},{key:"_findBorderPosition",value:function(t,e){var i=this.to,o=this.from;t.id===this.from.id&&(i=this.from,o=this.to);var n=Math.atan2(i.y-o.y,i.x-o.x),s=i.x-o.x,r=i.y-o.y,a=Math.sqrt(s*s+r*r),h=t.distanceToBorder(e,n),d=(a-h)/a,l={};return l.x=(1-d)*o.x+d*i.x,l.y=(1-d)*o.y+d*i.y,l}},{key:"_getDistanceToEdge",value:function(t,e,i,o,n,s){return this._getDistanceToLine(t,e,i,o,n,s)}}]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(8),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(120).default,u=i(221).default,c=i(222).default,p=i(223).default,f=i(224).default,m=i(121).default,v=i(225).default,g=i(226).default,y=i(2),b=i(119).default,_=function(){function t(e){(0,a.default)(this,t),this.body=e,this.physicsBody={physicsNodeIndices:[],physicsEdgeIndices:[],forces:{},velocities:{}},this.physicsEnabled=!0,this.simulationInterval=1e3/60,this.requiresTimeout=!0,this.previousStates={},this.referenceState={},this.freezeCache={},this.renderTimer=void 0,this.adaptiveTimestep=!1,this.adaptiveTimestepEnabled=!1,this.adaptiveCounter=0,this.adaptiveInterval=3,this.stabilized=!1,this.startedStabilization=!1,this.stabilizationIterations=0,this.ready=!1,this.options={},this.defaultOptions={enabled:!0,barnesHut:{theta:.5,gravitationalConstant:-2e3,centralGravity:.3,springLength:95,springConstant:.04,damping:.09,avoidOverlap:0},forceAtlas2Based:{theta:.5,gravitationalConstant:-50,centralGravity:.01,springConstant:.08,springLength:100,damping:.4,avoidOverlap:0},repulsion:{centralGravity:.2,springLength:200,springConstant:.05,nodeDistance:100,damping:.09,avoidOverlap:0},hierarchicalRepulsion:{centralGravity:0,springLength:100,springConstant:.01,nodeDistance:120,damping:.09},maxVelocity:50,minVelocity:.75,solver:"barnesHut",stabilization:{enabled:!0,iterations:1e3,updateInterval:50,onlyDynamicEdges:!1,fit:!0},timestep:.5,adaptiveTimestep:!0},y.extend(this.options,this.defaultOptions),this.timestep=.5,this.layoutFailed=!1,this.bindEventListeners()}return(0,d.default)(t,[{key:"bindEventListeners",value:function(){var t=this;this.body.emitter.on("initPhysics",function(){t.initPhysics()}),this.body.emitter.on("_layoutFailed",function(){t.layoutFailed=!0}),this.body.emitter.on("resetPhysics",function(){t.stopSimulation(),t.ready=!1}),this.body.emitter.on("disablePhysics",function(){t.physicsEnabled=!1,t.stopSimulation()}),this.body.emitter.on("restorePhysics",function(){t.setOptions(t.options),!0===t.ready&&t.startSimulation()}),this.body.emitter.on("startSimulation",function(){!0===t.ready&&t.startSimulation()}),this.body.emitter.on("stopSimulation",function(){t.stopSimulation()}),this.body.emitter.on("destroy",function(){t.stopSimulation(!1),t.body.emitter.off()}),this.body.emitter.on("_dataChanged",function(){t.updatePhysicsData()})}},{key:"setOptions",value:function(t){void 0!==t&&(!1===t?(this.options.enabled=!1,this.physicsEnabled=!1,this.stopSimulation()):!0===t?(this.options.enabled=!0,this.physicsEnabled=!0,this.startSimulation()):(this.physicsEnabled=!0,y.selectiveNotDeepExtend(["stabilization"],this.options,t),y.mergeOptions(this.options,t,"stabilization"),void 0===t.enabled&&(this.options.enabled=!0),!1===this.options.enabled&&(this.physicsEnabled=!1,this.stopSimulation()),this.timestep=this.options.timestep)),this.init()}},{key:"init",value:function(){var t;"forceAtlas2Based"===this.options.solver?(t=this.options.forceAtlas2Based,this.nodesSolver=new v(this.body,this.physicsBody,t),this.edgesSolver=new p(this.body,this.physicsBody,t),this.gravitySolver=new g(this.body,this.physicsBody,t)):"repulsion"===this.options.solver?(t=this.options.repulsion,this.nodesSolver=new u(this.body,this.physicsBody,t),this.edgesSolver=new p(this.body,this.physicsBody,t),this.gravitySolver=new m(this.body,this.physicsBody,t)):"hierarchicalRepulsion"===this.options.solver?(t=this.options.hierarchicalRepulsion,this.nodesSolver=new c(this.body,this.physicsBody,t),this.edgesSolver=new f(this.body,this.physicsBody,t),this.gravitySolver=new m(this.body,this.physicsBody,t)):(t=this.options.barnesHut,this.nodesSolver=new l(this.body,this.physicsBody,t),this.edgesSolver=new p(this.body,this.physicsBody,t),this.gravitySolver=new m(this.body,this.physicsBody,t)),this.modelOptions=t}},{key:"initPhysics",value:function(){!0===this.physicsEnabled&&!0===this.options.enabled?!0===this.options.stabilization.enabled?this.stabilize():(this.stabilized=!1,this.ready=!0,this.body.emitter.emit("fit",{},this.layoutFailed),this.startSimulation()):(this.ready=!0,this.body.emitter.emit("fit"))}},{key:"startSimulation",value:function(){!0===this.physicsEnabled&&!0===this.options.enabled?(this.stabilized=!1,this.adaptiveTimestep=!1,this.body.emitter.emit("_resizeNodes"),void 0===this.viewFunction&&(this.viewFunction=this.simulationStep.bind(this),this.body.emitter.on("initRedraw",this.viewFunction),this.body.emitter.emit("_startRendering"))):this.body.emitter.emit("_redraw")}},{key:"stopSimulation",value:function(){var t=!(arguments.length>0&&void 0!==arguments[0])||arguments[0];this.stabilized=!0,!0===t&&this._emitStabilized(),void 0!==this.viewFunction&&(this.body.emitter.off("initRedraw",this.viewFunction),this.viewFunction=void 0,!0===t&&this.body.emitter.emit("_stopRendering"))}},{key:"simulationStep",value:function(){var t=Date.now();this.physicsTick(),(Date.now()-t<.4*this.simulationInterval||!0===this.runDoubleSpeed)&&!1===this.stabilized&&(this.physicsTick(),this.runDoubleSpeed=!0),!0===this.stabilized&&this.stopSimulation()}},{key:"_emitStabilized",value:function(){var t=this,e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.stabilizationIterations;(this.stabilizationIterations>1||!0===this.startedStabilization)&&setTimeout(function(){t.body.emitter.emit("stabilized",{iterations:e}),t.startedStabilization=!1,t.stabilizationIterations=0},0)}},{key:"physicsStep",value:function(){this.gravitySolver.solve(),this.nodesSolver.solve(),this.edgesSolver.solve(),this.moveNodes()}},{key:"adjustTimeStep",value:function(){!0===this._evaluateStepQuality()?this.timestep=1.2*this.timestep:this.timestep/1.2.3))return!1;return!0}},{key:"moveNodes",value:function(){for(var t=this.physicsBody.physicsNodeIndices,e=0,i=0,o=0;oo&&(t=t>0?o:-o),t}},{key:"_performStep",value:function(t){var e=this.body.nodes[t],i=this.physicsBody.forces[t],o=this.physicsBody.velocities[t];return this.previousStates[t]={x:e.x,y:e.y,vx:o.x,vy:o.y},!1===e.options.fixed.x?(o.x=this.calculateComponentVelocity(o.x,i.x,e.options.mass),e.x+=o.x*this.timestep):(i.x=0,o.x=0),!1===e.options.fixed.y?(o.y=this.calculateComponentVelocity(o.y,i.y,e.options.mass),e.y+=o.y*this.timestep):(i.y=0,o.y=0),Math.sqrt(Math.pow(o.x,2)+Math.pow(o.y,2))}},{key:"_freezeNodes",value:function(){var t=this.body.nodes;for(var e in t)if(t.hasOwnProperty(e)&&t[e].x&&t[e].y){var i=t[e].options.fixed;this.freezeCache[e]={x:i.x,y:i.y},i.x=!0,i.y=!0}}},{key:"_restoreFrozenNodes",value:function(){var t=this.body.nodes;for(var e in t)t.hasOwnProperty(e)&&void 0!==this.freezeCache[e]&&(t[e].options.fixed.x=this.freezeCache[e].x,t[e].options.fixed.y=this.freezeCache[e].y);this.freezeCache={}}},{key:"stabilize",value:function(){var t=this,e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.options.stabilization.iterations;if("number"!=typeof e&&(e=this.options.stabilization.iterations,console.log("The stabilize method needs a numeric amount of iterations. Switching to default: ",e)),0===this.physicsBody.physicsNodeIndices.length)return void(this.ready=!0);this.adaptiveTimestep=this.options.adaptiveTimestep,this.body.emitter.emit("_resizeNodes"),this.stopSimulation(),this.stabilized=!1,this.body.emitter.emit("_blockRedraw"),this.targetIterations=e,!0===this.options.stabilization.onlyDynamicEdges&&this._freezeNodes(),this.stabilizationIterations=0,setTimeout(function(){return t._stabilizationBatch()},0)}},{key:"_startStabilizing",value:function(){return!0!==this.startedStabilization&&(this.body.emitter.emit("startStabilizing"),this.startedStabilization=!0,!0)}},{key:"_stabilizationBatch",value:function(){var t=this,e=function(){return!1===t.stabilized&&t.stabilizationIterations0){var s=n.edges.length+1,r=this.options.centralGravity*s*n.options.mass;o[n.id].x=e*r,o[n.id].y=i*r}}}]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(8),s=o(n),r=i(6),a=o(r),h=i(0),d=o(h),l=i(1),u=o(l),c=i(2),p=i(76).default,f=i(228).default,m=i(74).default,v=i(47).default,g=function(){function t(e){var i=this;(0,d.default)(this,t),this.body=e,this.clusteredNodes={},this.clusteredEdges={},this.options={},this.defaultOptions={},c.extend(this.options,this.defaultOptions),this.body.emitter.on("_resetData",function(){i.clusteredNodes={},i.clusteredEdges={}})}return(0,u.default)(t,[{key:"clusterByHubsize",value:function(t,e){void 0===t?t=this._getHubSize():"object"===(void 0===t?"undefined":(0,a.default)(t))&&(e=this._checkOptions(t),t=this._getHubSize());for(var i=[],o=0;o=t&&i.push(n.id)}for(var s=0;s0&&void 0!==arguments[0]?arguments[0]:{},i=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];if(void 0===e.joinCondition)throw new Error("Cannot call clusterByNodeData without a joinCondition function in the options.");e=this._checkOptions(e);var o={},n={};c.forEach(this.body.nodes,function(i,s){var r=p.cloneOptions(i);!0===e.joinCondition(r)&&(o[s]=i,c.forEach(i.edges,function(e){void 0===t.clusteredEdges[e.id]&&(n[e.id]=e)}))}),this._cluster(o,n,e,i)}},{key:"clusterByEdgeCount",value:function(t,e){var i=this,o=!(arguments.length>2&&void 0!==arguments[2])||arguments[2];e=this._checkOptions(e);for(var n=[],r={},a=void 0,h=void 0,d=void 0,l=0;l0&&(0,s.default)(m).length>0&&!0===b)if(c=function(){for(var t=0;t1&&void 0!==arguments[1])||arguments[1];this.clusterByEdgeCount(1,t,e)}},{key:"clusterBridges",value:function(t){var e=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];this.clusterByEdgeCount(2,t,e)}},{key:"clusterByConnection",value:function(t,e){var i=!(arguments.length>2&&void 0!==arguments[2])||arguments[2];if(void 0===t)throw new Error("No nodeId supplied to clusterByConnection!");if(void 0===this.body.nodes[t])throw new Error("The nodeId given to clusterByConnection does not exist!");var o=this.body.nodes[t];e=this._checkOptions(e,o),void 0===e.clusterNodeProperties.x&&(e.clusterNodeProperties.x=o.x),void 0===e.clusterNodeProperties.y&&(e.clusterNodeProperties.y=o.y),void 0===e.clusterNodeProperties.fixed&&(e.clusterNodeProperties.fixed={},e.clusterNodeProperties.fixed.x=o.options.fixed.x,e.clusterNodeProperties.fixed.y=o.options.fixed.y);var n={},r={},a=o.id,h=p.cloneOptions(o);n[a]=o;for(var d=0;d-1&&(r[g.id]=g)}this._cluster(n,r,e,i)}},{key:"_createClusterEdges",value:function(t,e,i,o){for(var n=void 0,r=void 0,a=void 0,h=void 0,d=void 0,l=void 0,u=(0,s.default)(t),c=[],p=0;p0&&void 0!==arguments[0]?arguments[0]:{};return void 0===t.clusterEdgeProperties&&(t.clusterEdgeProperties={}),void 0===t.clusterNodeProperties&&(t.clusterNodeProperties={}),t}},{key:"_cluster",value:function(t,e,i){var o=!(arguments.length>3&&void 0!==arguments[3])||arguments[3],n=[];for(var r in t)t.hasOwnProperty(r)&&void 0!==this.clusteredNodes[r]&&n.push(r);for(var a=0;ao?a.x:o,n=a.yr?a.y:r;return{x:.5*(i+o),y:.5*(n+r)}}},{key:"openCluster",value:function(t,e){var i=!(arguments.length>2&&void 0!==arguments[2])||arguments[2];if(void 0===t)throw new Error("No clusterNodeId supplied to openCluster.");var o=this.body.nodes[t];if(void 0===o)throw new Error("The clusterNodeId supplied to openCluster does not exist.");if(!0!==o.isCluster||void 0===o.containedNodes||void 0===o.containedEdges)throw new Error("The node:"+t+" is not a valid cluster.");var n=this.findNode(t),s=n.indexOf(t)-1;if(s>=0){var r=n[s];return this.body.nodes[r]._openChildCluster(t),delete this.body.nodes[t],void(!0===i&&this.body.emitter.emit("_dataChanged"))}var a=o.containedNodes,h=o.containedEdges;if(void 0!==e&&void 0!==e.releaseFunction&&"function"==typeof e.releaseFunction){var d={},l={x:o.x,y:o.y};for(var u in a)if(a.hasOwnProperty(u)){var p=this.body.nodes[u];d[u]={x:p.x,y:p.y}}var f=e.releaseFunction(l,d);for(var m in a)if(a.hasOwnProperty(m)){var v=this.body.nodes[m];void 0!==f[m]&&(v.x=void 0===f[m].x?o.x:f[m].x,v.y=void 0===f[m].y?o.y:f[m].y)}}else c.forEach(a,function(t){!1===t.options.fixed.x&&(t.x=o.x),!1===t.options.fixed.y&&(t.y=o.y)});for(var g in a)if(a.hasOwnProperty(g)){var y=this.body.nodes[g];y.vx=o.vx,y.vy=o.vy,y.setOptions({physics:!0}),delete this.clusteredNodes[g]}for(var b=[],_=0;_0&&n<100;){var s=e.pop();if(void 0!==s){var r=this.body.edges[s];if(void 0!==r){n++;var a=r.clusteringEdgeReplacingIds;if(void 0===a)o.push(s);else for(var h=0;ho&&(o=s.edges.length),t+=s.edges.length,e+=Math.pow(s.edges.length,2),i+=1}t/=i,e/=i;var r=e-Math.pow(t,2),a=Math.sqrt(r),h=Math.floor(t+2*a);return h>o&&(h=o),h}},{key:"_createClusteredEdge",value:function(t,e,i,o,n){var s=p.cloneOptions(i,"edge");c.deepExtend(s,o),s.from=t,s.to=e,s.id="clusterEdge:"+c.randomUUID(),void 0!==n&&c.deepExtend(s,n);var r=this.body.functions.createEdge(s);return r.clusteringEdgeReplacingIds=[i.id],r.connect(),this.body.edges[r.id]=r,r}},{key:"_clusterEdges",value:function(t,e,i,o){if(e instanceof m){var n=e,s={};s[n.id]=n,e=s}if(t instanceof v){var r=t,a={};a[r.id]=r,t=a}if(void 0===i||null===i)throw new Error("_clusterEdges: parameter clusterNode required");void 0===o&&(o=i.clusterEdgeProperties),this._createClusterEdges(t,e,i,o);for(var h in e)if(e.hasOwnProperty(h)&&void 0!==this.body.edges[h]){var d=this.body.edges[h];this._backupEdgeOptions(d),d.setOptions({physics:!1})}for(var l in t)t.hasOwnProperty(l)&&(this.clusteredNodes[l]={clusterId:i.id,node:this.body.nodes[l]},this.body.nodes[l].setOptions({physics:!1}))}},{key:"_getClusterNodeForNode",value:function(t){if(void 0!==t){var e=this.clusteredNodes[t];if(void 0!==e){var i=e.clusterId;if(void 0!==i)return this.body.nodes[i]}}}},{key:"_filter",value:function(t,e){var i=[];return c.forEach(t,function(t){e(t)&&i.push(t)}),i}},{key:"_updateState",value:function(){var t=this,e=void 0,i=[],o=[],n=function(e){c.forEach(t.body.nodes,function(t){!0===t.isCluster&&e(t)})};for(e in this.clusteredNodes)if(this.clusteredNodes.hasOwnProperty(e)){var r=this.body.nodes[e];void 0===r&&i.push(e)}n(function(t){for(var e=0;e0}e.endPointsValid()&&n||o.push(i)}),n(function(e){c.forEach(o,function(i){delete e.containedEdges[i],c.forEach(e.edges,function(n,s){if(n.id===i)return void(e.edges[s]=null);n.clusteringEdgeReplacingIds=t._filter(n.clusteringEdgeReplacingIds,function(t){return-1===o.indexOf(t)})}),e.edges=t._filter(e.edges,function(t){return null!==t})})}),c.forEach(o,function(e){delete t.clusteredEdges[e]}),c.forEach(o,function(e){delete t.body.edges[e]});var h=(0,s.default)(this.body.edges);c.forEach(h,function(e){var i=t.body.edges[e],o=t._isClusteredNode(i.fromId)||t._isClusteredNode(i.toId);if(o!==t._isClusteredEdge(i.id)){if(!o)throw new Error("remove edge from clustering not implemented!");var n=t._getClusterNodeForNode(i.fromId);void 0!==n&&t._clusterEdges(t.body.nodes[i.fromId],i,n);var s=t._getClusterNodeForNode(i.toId);void 0!==s&&t._clusterEdges(t.body.nodes[i.toId],i,s)}});for(var d=!1,l=!0;l;)!function(){var e=[];n(function(t){var i=(0,s.default)(t.containedNodes).length,o=!0===t.options.allowSingleNodeCluster;(o&&i<1||!o&&i<2)&&e.push(t.id)});for(var i=0;i0,d=d||l}();d&&this._updateState()}},{key:"_isClusteredNode",value:function(t){return void 0!==this.clusteredNodes[t]}},{key:"_isClusteredEdge",value:function(t){return void 0!==this.clusteredEdges[t]}}]),t}();e.default=g},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(2),m=i(47).default,v=function(t){function e(t,i,o,n,r,h){(0,a.default)(this,e);var d=(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o,n,r,h));return d.isCluster=!0,d.containedNodes={},d.containedEdges={},d}return(0,p.default)(e,t),(0,d.default)(e,[{key:"_openChildCluster",value:function(t){var e=this,i=this.body.nodes[t];if(void 0===this.containedNodes[t])throw new Error("node with id: "+t+" not in current cluster");if(!i.isCluster)throw new Error("node with id: "+t+" is not a cluster");delete this.containedNodes[t],f.forEach(i.edges,function(t){delete e.containedEdges[t.id]}),f.forEach(i.containedNodes,function(t,i){e.containedNodes[i]=t}),i.containedNodes={},f.forEach(i.containedEdges,function(t,i){e.containedEdges[i]=t}),i.containedEdges={},f.forEach(i.edges,function(t){f.forEach(e.edges,function(i){var o=i.clusteringEdgeReplacingIds.indexOf(t.id);-1!==o&&(f.forEach(t.clusteringEdgeReplacingIds,function(t){i.clusteringEdgeReplacingIds.push(t),e.body.edges[t].edgeReplacedById=i.id}),i.clusteringEdgeReplacingIds.splice(o,1))})}),i.edges=[]}}]),e}(m);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}function n(){var t;void 0!==window&&(t=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame),window.requestAnimationFrame=void 0===t?function(t){t()}:t}Object.defineProperty(e,"__esModule",{value:!0});var s=i(0),r=o(s),a=i(1),h=o(a),d=i(2),l=function(){function t(e,i){(0,r.default)(this,t),n(),this.body=e,this.canvas=i,this.redrawRequested=!1,this.renderTimer=void 0,this.requiresTimeout=!0,this.renderingActive=!1,this.renderRequests=0,this.allowRedraw=!0,this.dragging=!1,this.options={},this.defaultOptions={hideEdgesOnDrag:!1,hideNodesOnDrag:!1},d.extend(this.options,this.defaultOptions),this._determineBrowserMethod(),this.bindEventListeners()}return(0,h.default)(t,[{key:"bindEventListeners",value:function(){var t=this;this.body.emitter.on("dragStart",function(){t.dragging=!0}),this.body.emitter.on("dragEnd",function(){t.dragging=!1}),this.body.emitter.on("_resizeNodes",function(){t._resizeNodes()}),this.body.emitter.on("_redraw",function(){!1===t.renderingActive&&t._redraw()}),this.body.emitter.on("_blockRedraw",function(){t.allowRedraw=!1}),this.body.emitter.on("_allowRedraw",function(){t.allowRedraw=!0,t.redrawRequested=!1}),this.body.emitter.on("_requestRedraw",this._requestRedraw.bind(this)),this.body.emitter.on("_startRendering",function(){t.renderRequests+=1,t.renderingActive=!0,t._startRendering()}),this.body.emitter.on("_stopRendering",function(){t.renderRequests-=1,t.renderingActive=t.renderRequests>0,t.renderTimer=void 0}),this.body.emitter.on("destroy",function(){t.renderRequests=0,t.allowRedraw=!1,t.renderingActive=!1,!0===t.requiresTimeout?clearTimeout(t.renderTimer):window.cancelAnimationFrame(t.renderTimer),t.body.emitter.off()})}},{key:"setOptions",value:function(t){if(void 0!==t){var e=["hideEdgesOnDrag","hideNodesOnDrag"];d.selectiveDeepExtend(e,this.options,t)}}},{key:"_requestNextFrame",value:function(t,e){if("undefined"!=typeof window){var i=void 0,o=window;return!0===this.requiresTimeout?i=o.setTimeout(t,e):o.requestAnimationFrame&&(i=o.requestAnimationFrame(t)),i}}},{key:"_startRendering",value:function(){!0===this.renderingActive&&void 0===this.renderTimer&&(this.renderTimer=this._requestNextFrame(this._renderStep.bind(this),this.simulationInterval))}},{key:"_renderStep",value:function(){!0===this.renderingActive&&(this.renderTimer=void 0,!0===this.requiresTimeout&&this._startRendering(),this._redraw(),!1===this.requiresTimeout&&this._startRendering())}},{key:"redraw",value:function(){this.body.emitter.emit("setSize"),this._redraw()}},{key:"_requestRedraw",value:function(){var t=this;!0!==this.redrawRequested&&!1===this.renderingActive&&!0===this.allowRedraw&&(this.redrawRequested=!0,this._requestNextFrame(function(){t._redraw(!1)},0))}},{key:"_redraw",value:function(){var t=arguments.length>0&&void 0!==arguments[0]&&arguments[0];if(!0===this.allowRedraw){this.body.emitter.emit("initRedraw"),this.redrawRequested=!1,0!==this.canvas.frame.canvas.width&&0!==this.canvas.frame.canvas.height||this.canvas.setSize(),this.canvas.setTransform();var e=this.canvas.getContext(),i=this.canvas.frame.canvas.clientWidth,o=this.canvas.frame.canvas.clientHeight;if(e.clearRect(0,0,i,o),0===this.canvas.frame.clientWidth)return;e.save(),e.translate(this.body.view.translation.x,this.body.view.translation.y),e.scale(this.body.view.scale,this.body.view.scale),e.beginPath(),this.body.emitter.emit("beforeDrawing",e),e.closePath(),!1===t&&(!1===this.dragging||!0===this.dragging&&!1===this.options.hideEdgesOnDrag)&&this._drawEdges(e),(!1===this.dragging||!0===this.dragging&&!1===this.options.hideNodesOnDrag)&&this._drawNodes(e,t),e.beginPath(),this.body.emitter.emit("afterDrawing",e),e.closePath(),e.restore(),!0===t&&e.clearRect(0,0,i,o)}}},{key:"_resizeNodes",value:function(){this.canvas.setTransform();var t=this.canvas.getContext();t.save(),t.translate(this.body.view.translation.x,this.body.view.translation.y),t.scale(this.body.view.scale,this.body.view.scale);var e=this.body.nodes,i=void 0;for(var o in e)e.hasOwnProperty(o)&&(i=e[o],i.resize(t),i.updateBoundingBox(t,i.selected));t.restore()}},{key:"_drawNodes",value:function(t){for(var e=arguments.length>1&&void 0!==arguments[1]&&arguments[1],i=this.body.nodes,o=this.body.nodeIndices,n=void 0,s=[],r=this.canvas.DOMtoCanvas({x:-20,y:-20}),a=this.canvas.DOMtoCanvas({x:this.canvas.frame.canvas.clientWidth+20,y:this.canvas.frame.canvas.clientHeight+20}),h={top:r.y,left:r.x,bottom:a.y,right:a.x},d=0;d0&&void 0!==arguments[0]?arguments[0]:this.pixelRatio;!0===this.initialized&&(this.cameraState.previousWidth=this.frame.canvas.width/t,this.cameraState.previousHeight=this.frame.canvas.height/t,this.cameraState.scale=this.body.view.scale,this.cameraState.position=this.DOMtoCanvas({x:.5*this.frame.canvas.width/t,y:.5*this.frame.canvas.height/t}))}},{key:"_setCameraState",value:function(){if(void 0!==this.cameraState.scale&&0!==this.frame.canvas.clientWidth&&0!==this.frame.canvas.clientHeight&&0!==this.pixelRatio&&this.cameraState.previousWidth>0){var t=this.frame.canvas.width/this.pixelRatio/this.cameraState.previousWidth,e=this.frame.canvas.height/this.pixelRatio/this.cameraState.previousHeight,i=this.cameraState.scale;1!=t&&1!=e?i=.5*this.cameraState.scale*(t+e):1!=t?i=this.cameraState.scale*t:1!=e&&(i=this.cameraState.scale*e),this.body.view.scale=i;var o=this.DOMtoCanvas({x:.5*this.frame.canvas.clientWidth,y:.5*this.frame.canvas.clientHeight}),n={x:o.x-this.cameraState.position.x,y:o.y-this.cameraState.position.y};this.body.view.translation.x+=n.x*this.body.view.scale,this.body.view.translation.y+=n.y*this.body.view.scale}}},{key:"_prepareValue",value:function(t){if("number"==typeof t)return t+"px";if("string"==typeof t){if(-1!==t.indexOf("%")||-1!==t.indexOf("px"))return t;if(-1===t.indexOf("%"))return t+"px"}throw new Error("Could not use the value supplied for width or height:"+t)}},{key:"_create",value:function(){for(;this.body.container.hasChildNodes();)this.body.container.removeChild(this.body.container.firstChild);if(this.frame=document.createElement("div"),this.frame.className="vis-network",this.frame.style.position="relative",this.frame.style.overflow="hidden",this.frame.tabIndex=900,this.frame.canvas=document.createElement("canvas"),this.frame.canvas.style.position="relative",this.frame.appendChild(this.frame.canvas),this.frame.canvas.getContext)this._setPixelRatio(),this.setTransform();else{var t=document.createElement("DIV");t.style.color="red",t.style.fontWeight="bold",t.style.padding="10px",t.innerHTML="Error: your browser does not support HTML canvas",this.frame.canvas.appendChild(t)}this.body.container.appendChild(this.frame),this.body.view.scale=1,this.body.view.translation={x:.5*this.frame.canvas.clientWidth,y:.5*this.frame.canvas.clientHeight},this._bindHammer()}},{key:"_bindHammer",value:function(){var t=this;void 0!==this.hammer&&this.hammer.destroy(),this.drag={},this.pinch={},this.hammer=new h(this.frame.canvas),this.hammer.get("pinch").set({enable:!0}),this.hammer.get("pan").set({threshold:5,direction:h.DIRECTION_ALL}),d.onTouch(this.hammer,function(e){t.body.eventListeners.onTouch(e)}),this.hammer.on("tap",function(e){t.body.eventListeners.onTap(e)}),this.hammer.on("doubletap",function(e){t.body.eventListeners.onDoubleTap(e)}),this.hammer.on("press",function(e){t.body.eventListeners.onHold(e)}),this.hammer.on("panstart",function(e){t.body.eventListeners.onDragStart(e)}),this.hammer.on("panmove",function(e){t.body.eventListeners.onDrag(e)}),this.hammer.on("panend",function(e){t.body.eventListeners.onDragEnd(e)}),this.hammer.on("pinch",function(e){t.body.eventListeners.onPinch(e)}),this.frame.canvas.addEventListener("mousewheel",function(e){t.body.eventListeners.onMouseWheel(e)}),this.frame.canvas.addEventListener("DOMMouseScroll",function(e){t.body.eventListeners.onMouseWheel(e)}),this.frame.canvas.addEventListener("mousemove",function(e){t.body.eventListeners.onMouseMove(e)}),this.frame.canvas.addEventListener("contextmenu",function(e){t.body.eventListeners.onContext(e)}),this.hammerFrame=new h(this.frame),d.onRelease(this.hammerFrame,function(e){t.body.eventListeners.onRelease(e)})}},{key:"setSize",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.options.width,e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.options.height;t=this._prepareValue(t),e=this._prepareValue(e);var i=!1,o=this.frame.canvas.width,n=this.frame.canvas.height,s=this.pixelRatio;if(this._setPixelRatio(),t!=this.options.width||e!=this.options.height||this.frame.style.width!=t||this.frame.style.height!=e)this._getCameraState(s),this.frame.style.width=t,this.frame.style.height=e,this.frame.canvas.style.width="100%",this.frame.canvas.style.height="100%",this.frame.canvas.width=Math.round(this.frame.canvas.clientWidth*this.pixelRatio),this.frame.canvas.height=Math.round(this.frame.canvas.clientHeight*this.pixelRatio),this.options.width=t,this.options.height=e,this.canvasViewCenter={x:.5*this.frame.clientWidth,y:.5*this.frame.clientHeight},i=!0;else{var r=Math.round(this.frame.canvas.clientWidth*this.pixelRatio),a=Math.round(this.frame.canvas.clientHeight*this.pixelRatio) ;this.frame.canvas.width===r&&this.frame.canvas.height===a||this._getCameraState(s),this.frame.canvas.width!==r&&(this.frame.canvas.width=r,i=!0),this.frame.canvas.height!==a&&(this.frame.canvas.height=a,i=!0)}return!0===i&&(this.body.emitter.emit("resize",{width:Math.round(this.frame.canvas.width/this.pixelRatio),height:Math.round(this.frame.canvas.height/this.pixelRatio),oldWidth:Math.round(o/this.pixelRatio),oldHeight:Math.round(n/this.pixelRatio)}),this._setCameraState()),this.initialized=!0,i}},{key:"getContext",value:function(){return this.frame.canvas.getContext("2d")}},{key:"_determinePixelRatio",value:function(){var t=this.getContext();if(void 0===t)throw new Error("Could not get canvax context");var e=1;return"undefined"!=typeof window&&(e=window.devicePixelRatio||1),e/(t.webkitBackingStorePixelRatio||t.mozBackingStorePixelRatio||t.msBackingStorePixelRatio||t.oBackingStorePixelRatio||t.backingStorePixelRatio||1)}},{key:"_setPixelRatio",value:function(){this.pixelRatio=this._determinePixelRatio()}},{key:"setTransform",value:function(){var t=this.getContext();if(void 0===t)throw new Error("Could not get canvax context");t.setTransform(this.pixelRatio,0,0,this.pixelRatio,0,0)}},{key:"_XconvertDOMtoCanvas",value:function(t){return(t-this.body.view.translation.x)/this.body.view.scale}},{key:"_XconvertCanvasToDOM",value:function(t){return t*this.body.view.scale+this.body.view.translation.x}},{key:"_YconvertDOMtoCanvas",value:function(t){return(t-this.body.view.translation.y)/this.body.view.scale}},{key:"_YconvertCanvasToDOM",value:function(t){return t*this.body.view.scale+this.body.view.translation.y}},{key:"canvasToDOM",value:function(t){return{x:this._XconvertCanvasToDOM(t.x),y:this._YconvertCanvasToDOM(t.y)}}},{key:"DOMtoCanvas",value:function(t){return{x:this._XconvertDOMtoCanvas(t.x),y:this._YconvertDOMtoCanvas(t.y)}}}]),t}();e.default=u},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(0),s=o(n),r=i(1),a=o(r),h=i(2),d=i(76).default,l=function(){function t(e,i){var o=this;(0,s.default)(this,t),this.body=e,this.canvas=i,this.animationSpeed=1/this.renderRefreshRate,this.animationEasingFunction="easeInOutQuint",this.easingTime=0,this.sourceScale=0,this.targetScale=0,this.sourceTranslation=0,this.targetTranslation=0,this.lockedOnNodeId=void 0,this.lockedOnNodeOffset=void 0,this.touchTime=0,this.viewFunction=void 0,this.body.emitter.on("fit",this.fit.bind(this)),this.body.emitter.on("animationFinished",function(){o.body.emitter.emit("_stopRendering")}),this.body.emitter.on("unlockNode",this.releaseNode.bind(this))}return(0,a.default)(t,[{key:"setOptions",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.options=t}},{key:"fit",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{nodes:[]},e=arguments.length>1&&void 0!==arguments[1]&&arguments[1],i=void 0,o=void 0;if(void 0!==t.nodes&&0!==t.nodes.length||(t.nodes=this.body.nodeIndices),!0===e){var n=0;for(var s in this.body.nodes)if(this.body.nodes.hasOwnProperty(s)){var r=this.body.nodes[s];!0===r.predefinedPosition&&(n+=1)}if(n>.5*this.body.nodeIndices.length)return void this.fit(t,!1);i=d.getRange(this.body.nodes,t.nodes);o=12.662/(this.body.nodeIndices.length+7.4147)+.0964822;o*=Math.min(this.canvas.frame.canvas.clientWidth/600,this.canvas.frame.canvas.clientHeight/600)}else{this.body.emitter.emit("_resizeNodes"),i=d.getRange(this.body.nodes,t.nodes);var a=1.1*Math.abs(i.maxX-i.minX),h=1.1*Math.abs(i.maxY-i.minY),l=this.canvas.frame.canvas.clientWidth/a,u=this.canvas.frame.canvas.clientHeight/h;o=l<=u?l:u}o>1?o=1:0===o&&(o=1);var c=d.findCenter(i),p={position:c,scale:o,animation:t.animation};this.moveTo(p)}},{key:"focus",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(void 0!==this.body.nodes[t]){var i={x:this.body.nodes[t].x,y:this.body.nodes[t].y};e.position=i,e.lockedOnNode=t,this.moveTo(e)}else console.log("Node: "+t+" cannot be found.")}},{key:"moveTo",value:function(t){if(void 0===t)return void(t={});void 0===t.offset&&(t.offset={x:0,y:0}),void 0===t.offset.x&&(t.offset.x=0),void 0===t.offset.y&&(t.offset.y=0),void 0===t.scale&&(t.scale=this.body.view.scale),void 0===t.position&&(t.position=this.getViewPosition()),void 0===t.animation&&(t.animation={duration:0}),!1===t.animation&&(t.animation={duration:0}),!0===t.animation&&(t.animation={}),void 0===t.animation.duration&&(t.animation.duration=1e3),void 0===t.animation.easingFunction&&(t.animation.easingFunction="easeInOutQuad"),this.animateView(t)}},{key:"animateView",value:function(t){if(void 0!==t){this.animationEasingFunction=t.animation.easingFunction,this.releaseNode(),!0===t.locked&&(this.lockedOnNodeId=t.lockedOnNode,this.lockedOnNodeOffset=t.offset),0!=this.easingTime&&this._transitionRedraw(!0),this.sourceScale=this.body.view.scale,this.sourceTranslation=this.body.view.translation,this.targetScale=t.scale,this.body.view.scale=this.targetScale;var e=this.canvas.DOMtoCanvas({x:.5*this.canvas.frame.canvas.clientWidth,y:.5*this.canvas.frame.canvas.clientHeight}),i={x:e.x-t.position.x,y:e.y-t.position.y};this.targetTranslation={x:this.sourceTranslation.x+i.x*this.targetScale+t.offset.x,y:this.sourceTranslation.y+i.y*this.targetScale+t.offset.y},0===t.animation.duration?void 0!=this.lockedOnNodeId?(this.viewFunction=this._lockedRedraw.bind(this),this.body.emitter.on("initRedraw",this.viewFunction)):(this.body.view.scale=this.targetScale,this.body.view.translation=this.targetTranslation,this.body.emitter.emit("_requestRedraw")):(this.animationSpeed=1/(60*t.animation.duration*.001)||1/60,this.animationEasingFunction=t.animation.easingFunction,this.viewFunction=this._transitionRedraw.bind(this),this.body.emitter.on("initRedraw",this.viewFunction),this.body.emitter.emit("_startRendering"))}}},{key:"_lockedRedraw",value:function(){var t={x:this.body.nodes[this.lockedOnNodeId].x,y:this.body.nodes[this.lockedOnNodeId].y},e=this.canvas.DOMtoCanvas({x:.5*this.canvas.frame.canvas.clientWidth,y:.5*this.canvas.frame.canvas.clientHeight}),i={x:e.x-t.x,y:e.y-t.y},o=this.body.view.translation,n={x:o.x+i.x*this.body.view.scale+this.lockedOnNodeOffset.x,y:o.y+i.y*this.body.view.scale+this.lockedOnNodeOffset.y};this.body.view.translation=n}},{key:"releaseNode",value:function(){void 0!==this.lockedOnNodeId&&void 0!==this.viewFunction&&(this.body.emitter.off("initRedraw",this.viewFunction),this.lockedOnNodeId=void 0,this.lockedOnNodeOffset=void 0)}},{key:"_transitionRedraw",value:function(){var t=arguments.length>0&&void 0!==arguments[0]&&arguments[0];this.easingTime+=this.animationSpeed,this.easingTime=!0===t?1:this.easingTime;var e=h.easingFunctions[this.animationEasingFunction](this.easingTime);this.body.view.scale=this.sourceScale+(this.targetScale-this.sourceScale)*e,this.body.view.translation={x:this.sourceTranslation.x+(this.targetTranslation.x-this.sourceTranslation.x)*e,y:this.sourceTranslation.y+(this.targetTranslation.y-this.sourceTranslation.y)*e},this.easingTime>=1&&(this.body.emitter.off("initRedraw",this.viewFunction),this.easingTime=0,void 0!=this.lockedOnNodeId&&(this.viewFunction=this._lockedRedraw.bind(this),this.body.emitter.on("initRedraw",this.viewFunction)),this.body.emitter.emit("animationFinished"))}},{key:"getScale",value:function(){return this.body.view.scale}},{key:"getViewPosition",value:function(){return this.canvas.DOMtoCanvas({x:.5*this.canvas.frame.canvas.clientWidth,y:.5*this.canvas.frame.canvas.clientHeight})}}]),t}();e.default=l},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(0),s=o(n),r=i(1),a=o(r),h=i(2),d=i(233).default,l=i(104).default,u=function(){function t(e,i,o){(0,s.default)(this,t),this.body=e,this.canvas=i,this.selectionHandler=o,this.navigationHandler=new d(e,i),this.body.eventListeners.onTap=this.onTap.bind(this),this.body.eventListeners.onTouch=this.onTouch.bind(this),this.body.eventListeners.onDoubleTap=this.onDoubleTap.bind(this),this.body.eventListeners.onHold=this.onHold.bind(this),this.body.eventListeners.onDragStart=this.onDragStart.bind(this),this.body.eventListeners.onDrag=this.onDrag.bind(this),this.body.eventListeners.onDragEnd=this.onDragEnd.bind(this),this.body.eventListeners.onMouseWheel=this.onMouseWheel.bind(this),this.body.eventListeners.onPinch=this.onPinch.bind(this),this.body.eventListeners.onMouseMove=this.onMouseMove.bind(this),this.body.eventListeners.onRelease=this.onRelease.bind(this),this.body.eventListeners.onContext=this.onContext.bind(this),this.touchTime=0,this.drag={},this.pinch={},this.popup=void 0,this.popupObj=void 0,this.popupTimer=void 0,this.body.functions.getPointer=this.getPointer.bind(this),this.options={},this.defaultOptions={dragNodes:!0,dragView:!0,hover:!1,keyboard:{enabled:!1,speed:{x:10,y:10,zoom:.02},bindToWindow:!0},navigationButtons:!1,tooltipDelay:300,zoomView:!0},h.extend(this.options,this.defaultOptions),this.bindEventListeners()}return(0,a.default)(t,[{key:"bindEventListeners",value:function(){var t=this;this.body.emitter.on("destroy",function(){clearTimeout(t.popupTimer),delete t.body.functions.getPointer})}},{key:"setOptions",value:function(t){if(void 0!==t){var e=["hideEdgesOnDrag","hideNodesOnDrag","keyboard","multiselect","selectable","selectConnectedEdges"];h.selectiveNotDeepExtend(e,this.options,t),h.mergeOptions(this.options,t,"keyboard"),t.tooltip&&(h.extend(this.options.tooltip,t.tooltip),t.tooltip.color&&(this.options.tooltip.color=h.parseColor(t.tooltip.color)))}this.navigationHandler.setOptions(this.options)}},{key:"getPointer",value:function(t){return{x:t.x-h.getAbsoluteLeft(this.canvas.frame.canvas),y:t.y-h.getAbsoluteTop(this.canvas.frame.canvas)}}},{key:"onTouch",value:function(t){(new Date).valueOf()-this.touchTime>50&&(this.drag.pointer=this.getPointer(t.center),this.drag.pinched=!1,this.pinch.scale=this.body.view.scale,this.touchTime=(new Date).valueOf())}},{key:"onTap",value:function(t){var e=this.getPointer(t.center),i=this.selectionHandler.options.multiselect&&(t.changedPointers[0].ctrlKey||t.changedPointers[0].metaKey);this.checkSelectionChanges(e,t,i),this.selectionHandler._generateClickEvent("click",t,e)}},{key:"onDoubleTap",value:function(t){var e=this.getPointer(t.center);this.selectionHandler._generateClickEvent("doubleClick",t,e)}},{key:"onHold",value:function(t){var e=this.getPointer(t.center),i=this.selectionHandler.options.multiselect;this.checkSelectionChanges(e,t,i),this.selectionHandler._generateClickEvent("click",t,e),this.selectionHandler._generateClickEvent("hold",t,e)}},{key:"onRelease",value:function(t){if((new Date).valueOf()-this.touchTime>10){var e=this.getPointer(t.center);this.selectionHandler._generateClickEvent("release",t,e),this.touchTime=(new Date).valueOf()}}},{key:"onContext",value:function(t){var e=this.getPointer({x:t.clientX,y:t.clientY});this.selectionHandler._generateClickEvent("oncontext",t,e)}},{key:"checkSelectionChanges",value:function(t,e){var i=arguments.length>2&&void 0!==arguments[2]&&arguments[2],o=this.selectionHandler.getSelection(),n=!1;n=!0===i?this.selectionHandler.selectAdditionalOnPoint(t):this.selectionHandler.selectOnPoint(t);var s=this.selectionHandler.getSelection(),r=this._determineDifference(o,s),a=this._determineDifference(s,o);r.edges.length>0&&(this.selectionHandler._generateClickEvent("deselectEdge",e,t,o),n=!0),r.nodes.length>0&&(this.selectionHandler._generateClickEvent("deselectNode",e,t,o),n=!0),a.nodes.length>0&&(this.selectionHandler._generateClickEvent("selectNode",e,t),n=!0),a.edges.length>0&&(this.selectionHandler._generateClickEvent("selectEdge",e,t),n=!0),!0===n&&this.selectionHandler._generateClickEvent("select",e,t)}},{key:"_determineDifference",value:function(t,e){var i=function(t,e){for(var i=[],o=0;o10&&(t=10);var o=void 0;void 0!==this.drag&&!0===this.drag.dragging&&(o=this.canvas.DOMtoCanvas(this.drag.pointer));var n=this.body.view.translation,s=t/i,r=(1-s)*e.x+n.x*s,a=(1-s)*e.y+n.y*s;if(this.body.view.scale=t,this.body.view.translation={x:r,y:a},void 0!=o){var h=this.canvas.canvasToDOM(o);this.drag.pointer.x=h.x,this.drag.pointer.y=h.y}this.body.emitter.emit("_requestRedraw"),i0&&(this.popupObj=h[u[u.length-1]],s=!0)}if(void 0===this.popupObj&&!1===s){for(var p=this.body.edgeIndices,f=this.body.edges,m=void 0,v=[],g=0;g0&&(this.popupObj=f[v[v.length-1]],r="edge")}void 0!==this.popupObj?this.popupObj.id!==n&&(void 0===this.popup&&(this.popup=new l(this.canvas.frame)),this.popup.popupTargetType=r,this.popup.popupTargetId=this.popupObj.id,this.popup.setPosition(t.x+3,t.y-5),this.popup.setText(this.popupObj.getTitle()),this.popup.show(),this.body.emitter.emit("showPopup",this.popupObj.id)):void 0!==this.popup&&(this.popup.hide(),this.body.emitter.emit("hidePopup"))}},{key:"_checkHidePopup",value:function(t){var e=this.selectionHandler._pointerToPositionObject(t),i=!1;if("node"===this.popup.popupTargetType){if(void 0!==this.body.nodes[this.popup.popupTargetId]&&!0===(i=this.body.nodes[this.popup.popupTargetId].isOverlappingWith(e))){var o=this.selectionHandler.getNodeAt(t);i=void 0!==o&&o.id===this.popup.popupTargetId}}else void 0===this.selectionHandler.getNodeAt(t)&&void 0!==this.body.edges[this.popup.popupTargetId]&&(i=this.body.edges[this.popup.popupTargetId].isOverlappingWith(e));!1===i&&(this.popupObj=void 0,this.popup.hide(),this.body.emitter.emit("hidePopup"))}}]),t}();e.default=u},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(0),s=o(n),r=i(1),a=o(r),h=i(10),d=i(37),l=i(35),u=function(){function t(e,i){var o=this;(0,s.default)(this,t),this.body=e,this.canvas=i,this.iconsCreated=!1,this.navigationHammers=[],this.boundFunctions={},this.touchTime=0,this.activated=!1,this.body.emitter.on("activate",function(){o.activated=!0,o.configureKeyboardBindings()}),this.body.emitter.on("deactivate",function(){o.activated=!1,o.configureKeyboardBindings()}),this.body.emitter.on("destroy",function(){void 0!==o.keycharm&&o.keycharm.destroy()}),this.options={}}return(0,a.default)(t,[{key:"setOptions",value:function(t){void 0!==t&&(this.options=t,this.create())}},{key:"create",value:function(){!0===this.options.navigationButtons?!1===this.iconsCreated&&this.loadNavigationElements():!0===this.iconsCreated&&this.cleanNavigation(),this.configureKeyboardBindings()}},{key:"cleanNavigation",value:function(){if(0!=this.navigationHammers.length){for(var t=0;t700&&(this.body.emitter.emit("fit",{duration:700}),this.touchTime=(new Date).valueOf())}},{key:"_stopMovement",value:function(){for(var t in this.boundFunctions)this.boundFunctions.hasOwnProperty(t)&&(this.body.emitter.off("initRedraw",this.boundFunctions[t]),this.body.emitter.emit("_stopRendering"));this.boundFunctions={}}},{key:"_moveUp",value:function(){this.body.view.translation.y+=this.options.keyboard.speed.y}},{key:"_moveDown",value:function(){this.body.view.translation.y-=this.options.keyboard.speed.y}},{key:"_moveLeft",value:function(){this.body.view.translation.x+=this.options.keyboard.speed.x}},{key:"_moveRight",value:function(){this.body.view.translation.x-=this.options.keyboard.speed.x}},{key:"_zoomIn",value:function(){var t=this.body.view.scale,e=this.body.view.scale*(1+this.options.keyboard.speed.zoom),i=this.body.view.translation,o=e/t,n=(1-o)*this.canvas.canvasViewCenter.x+i.x*o,s=(1-o)*this.canvas.canvasViewCenter.y+i.y*o;this.body.view.scale=e,this.body.view.translation={x:n,y:s},this.body.emitter.emit("zoom",{direction:"+",scale:this.body.view.scale,pointer:null})}},{key:"_zoomOut",value:function(){var t=this.body.view.scale,e=this.body.view.scale/(1+this.options.keyboard.speed.zoom),i=this.body.view.translation,o=e/t,n=(1-o)*this.canvas.canvasViewCenter.x+i.x*o,s=(1-o)*this.canvas.canvasViewCenter.y+i.y*o;this.body.view.scale=e,this.body.view.translation={x:n,y:s},this.body.emitter.emit("zoom",{direction:"-",scale:this.body.view.scale,pointer:null})}},{key:"configureKeyboardBindings",value:function(){var t=this;void 0!==this.keycharm&&this.keycharm.destroy(),!0===this.options.keyboard.enabled&&(!0===this.options.keyboard.bindToWindow?this.keycharm=l({container:window,preventDefault:!0}):this.keycharm=l({container:this.canvas.frame,preventDefault:!0}),this.keycharm.reset(),!0===this.activated&&(this.keycharm.bind("up",function(){t.bindToRedraw("_moveUp")},"keydown"),this.keycharm.bind("down",function(){t.bindToRedraw("_moveDown")},"keydown"),this.keycharm.bind("left",function(){t.bindToRedraw("_moveLeft")},"keydown"),this.keycharm.bind("right",function(){t.bindToRedraw("_moveRight")},"keydown"),this.keycharm.bind("=",function(){t.bindToRedraw("_zoomIn")},"keydown"),this.keycharm.bind("num+",function(){t.bindToRedraw("_zoomIn")},"keydown"),this.keycharm.bind("num-",function(){t.bindToRedraw("_zoomOut")},"keydown"),this.keycharm.bind("-",function(){t.bindToRedraw("_zoomOut")},"keydown"),this.keycharm.bind("[",function(){t.bindToRedraw("_zoomOut")},"keydown"),this.keycharm.bind("]",function(){t.bindToRedraw("_zoomIn")},"keydown"),this.keycharm.bind("pageup",function(){t.bindToRedraw("_zoomIn")},"keydown"),this.keycharm.bind("pagedown",function(){t.bindToRedraw("_zoomOut")},"keydown"),this.keycharm.bind("up",function(){t.unbindFromRedraw("_moveUp")},"keyup"),this.keycharm.bind("down",function(){t.unbindFromRedraw("_moveDown")},"keyup"),this.keycharm.bind("left",function(){t.unbindFromRedraw("_moveLeft")},"keyup"),this.keycharm.bind("right",function(){t.unbindFromRedraw("_moveRight")},"keyup"),this.keycharm.bind("=",function(){t.unbindFromRedraw("_zoomIn")},"keyup"),this.keycharm.bind("num+",function(){t.unbindFromRedraw("_zoomIn")},"keyup"),this.keycharm.bind("num-",function(){t.unbindFromRedraw("_zoomOut")},"keyup"),this.keycharm.bind("-",function(){t.unbindFromRedraw("_zoomOut")},"keyup"),this.keycharm.bind("[",function(){t.unbindFromRedraw("_zoomOut")},"keyup"),this.keycharm.bind("]",function(){t.unbindFromRedraw("_zoomIn")},"keyup"),this.keycharm.bind("pageup",function(){t.unbindFromRedraw("_zoomIn")},"keyup"),this.keycharm.bind("pagedown",function(){t.unbindFromRedraw("_zoomOut")},"keyup")))}}]),t}();e.default=u},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(0),s=o(n),r=i(1),a=o(r),h=i(47).default,d=i(74).default,l=i(2),u=function(){function t(e,i){var o=this;(0,s.default)(this,t),this.body=e,this.canvas=i,this.selectionObj={nodes:[],edges:[]},this.hoverObj={nodes:{},edges:{}},this.options={},this.defaultOptions={multiselect:!1,selectable:!0,selectConnectedEdges:!0,hoverConnectedEdges:!0},l.extend(this.options,this.defaultOptions),this.body.emitter.on("_dataChanged",function(){o.updateSelection()})}return(0,a.default)(t,[{key:"setOptions",value:function(t){if(void 0!==t){var e=["multiselect","hoverConnectedEdges","selectable","selectConnectedEdges"];l.selectiveDeepExtend(e,this.options,t)}}},{key:"selectOnPoint",value:function(t){var e=!1;if(!0===this.options.selectable){var i=this.getNodeAt(t)||this.getEdgeAt(t);this.unselectAll(),void 0!==i&&(e=this.selectObject(i)),this.body.emitter.emit("_requestRedraw")}return e}},{key:"selectAdditionalOnPoint",value:function(t){var e=!1;if(!0===this.options.selectable){var i=this.getNodeAt(t)||this.getEdgeAt(t);void 0!==i&&(e=!0,!0===i.isSelected()?this.deselectObject(i):this.selectObject(i),this.body.emitter.emit("_requestRedraw"))}return e}},{key:"_initBaseEvent",value:function(t,e){var i={};return i.pointer={DOM:{x:e.x,y:e.y},canvas:this.canvas.DOMtoCanvas(e)},i.event=t,i}},{key:"_generateClickEvent",value:function(t,e,i,o){var n=arguments.length>4&&void 0!==arguments[4]&&arguments[4],s=this._initBaseEvent(e,i);if(!0===n)s.nodes=[],s.edges=[];else{var r=this.getSelection();s.nodes=r.nodes,s.edges=r.edges}void 0!==o&&(s.previousSelection=o),"click"==t&&(s.items=this.getClickedItems(i)),this.body.emitter.emit(t,s)}},{key:"selectObject",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.options.selectConnectedEdges;return void 0!==t&&(t instanceof h&&!0===e&&this._selectConnectedEdges(t),t.select(),this._addToSelection(t),!0)}},{key:"deselectObject",value:function(t){!0===t.isSelected()&&(t.selected=!1,this._removeFromSelection(t))}},{key:"_getAllNodesOverlappingWith",value:function(t){for(var e=[],i=this.body.nodes,o=0;o1&&void 0!==arguments[1])||arguments[1],i=this._pointerToPositionObject(t),o=this._getAllNodesOverlappingWith(i);return o.length>0?!0===e?this.body.nodes[o[o.length-1]]:o[o.length-1]:void 0}},{key:"_getEdgesOverlappingWith",value:function(t,e){for(var i=this.body.edges,o=0;o1&&void 0!==arguments[1])||arguments[1],i=this.canvas.DOMtoCanvas(t),o=10,n=null,s=this.body.edges,r=0;r1)return!0;return!1}},{key:"_selectConnectedEdges",value:function(t){for(var e=0;e1&&void 0!==arguments[1]?arguments[1]:{},i=void 0,o=void 0 ;if(!t||!t.nodes&&!t.edges)throw"Selection must be an object with nodes and/or edges properties";if((e.unselectAll||void 0===e.unselectAll)&&this.unselectAll(),t.nodes)for(i=0;i1&&void 0!==arguments[1])||arguments[1];if(!t||void 0===t.length)throw"Selection must be an array with ids";this.setSelection({nodes:t},{highlightEdges:e})}},{key:"selectEdges",value:function(t){if(!t||void 0===t.length)throw"Selection must be an array with ids";this.setSelection({edges:t})}},{key:"updateSelection",value:function(){for(var t in this.selectionObj.nodes)this.selectionObj.nodes.hasOwnProperty(t)&&(this.body.nodes.hasOwnProperty(t)||delete this.selectionObj.nodes[t]);for(var e in this.selectionObj.edges)this.selectionObj.edges.hasOwnProperty(e)&&(this.body.edges.hasOwnProperty(e)||delete this.selectionObj.edges[e])}},{key:"getClickedItems",value:function(t){for(var e=this.canvas.DOMtoCanvas(t),i=[],o=this.body.nodeIndices,n=this.body.nodes,s=o.length-1;s>=0;s--){var r=n[o[s]],a=r.getItemsOnPoint(e);i.push.apply(i,a)}for(var h=this.body.edgeIndices,d=this.body.edges,l=h.length-1;l>=0;l--){var u=d[h[l]],c=u.getItemsOnPoint(e);i.push.apply(i,c)}return i}}]),t}();e.default=u},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(30),s=o(n),r=i(6),a=o(r),h=i(8),d=o(h),l=i(0),u=o(l),c=i(1),p=o(c),f=i(2),m=i(76).default,v=i(236),g=v.HorizontalStrategy,y=v.VerticalStrategy,b=function(){function t(){(0,u.default)(this,t),this.childrenReference={},this.parentReference={},this.trees={},this.distributionOrdering={},this.levels={},this.distributionIndex={},this.isTree=!1,this.treeIndex=-1}return(0,p.default)(t,[{key:"addRelation",value:function(t,e){void 0===this.childrenReference[t]&&(this.childrenReference[t]=[]),this.childrenReference[t].push(e),void 0===this.parentReference[e]&&(this.parentReference[e]=[]),this.parentReference[e].push(t)}},{key:"checkIfTree",value:function(){for(var t in this.parentReference)if(this.parentReference[t].length>1)return void(this.isTree=!1);this.isTree=!0}},{key:"numTrees",value:function(){return this.treeIndex+1}},{key:"setTreeIndex",value:function(t,e){void 0!==e&&void 0===this.trees[t.id]&&(this.trees[t.id]=e,this.treeIndex=Math.max(e,this.treeIndex))}},{key:"ensureLevel",value:function(t){void 0===this.levels[t]&&(this.levels[t]=0)}},{key:"getMaxLevel",value:function(t){var e=this,i={};return function t(o){if(void 0!==i[o])return i[o];var n=e.levels[o];if(e.childrenReference[o]){var s=e.childrenReference[o];if(s.length>0)for(var r=0;r0&&(i.levelSeparation*=-1):i.levelSeparation<0&&(i.levelSeparation*=-1),this.setDirectionStrategy(),this.body.emitter.emit("_resetHierarchicalLayout"),this.adaptAllOptionsForHierarchicalLayout(e);if(!0===o)return this.body.emitter.emit("refresh"),f.deepExtend(e,this.optionsBackup)}return e}},{key:"adaptAllOptionsForHierarchicalLayout",value:function(t){if(!0===this.options.hierarchical.enabled){var e=this.optionsBackup.physics;void 0===t.physics||!0===t.physics?(t.physics={enabled:void 0===e.enabled||e.enabled,solver:"hierarchicalRepulsion"},e.enabled=void 0===e.enabled||e.enabled,e.solver=e.solver||"barnesHut"):"object"===(0,a.default)(t.physics)?(e.enabled=void 0===t.physics.enabled||t.physics.enabled,e.solver=t.physics.solver||"barnesHut",t.physics.solver="hierarchicalRepulsion"):!1!==t.physics&&(e.solver="barnesHut",t.physics={solver:"hierarchicalRepulsion"});var i=this.direction.curveType();if(void 0===t.edges)this.optionsBackup.edges={smooth:{enabled:!0,type:"dynamic"}},t.edges={smooth:!1};else if(void 0===t.edges.smooth)this.optionsBackup.edges={smooth:{enabled:!0,type:"dynamic"}},t.edges.smooth=!1;else if("boolean"==typeof t.edges.smooth)this.optionsBackup.edges={smooth:t.edges.smooth},t.edges.smooth={enabled:t.edges.smooth,type:i};else{var o=t.edges.smooth;void 0!==o.type&&"dynamic"!==o.type&&(i=o.type),this.optionsBackup.edges={smooth:void 0===o.enabled||o.enabled,type:void 0===o.type?"dynamic":o.type,roundness:void 0===o.roundness?.5:o.roundness,forceDirection:void 0!==o.forceDirection&&o.forceDirection},t.edges.smooth={enabled:void 0===o.enabled||o.enabled,type:i,roundness:void 0===o.roundness?.5:o.roundness,forceDirection:void 0!==o.forceDirection&&o.forceDirection}}this.body.emitter.emit("_forceDisableDynamicCurves",i)}return t}},{key:"seededRandom",value:function(){var t=1e4*Math.sin(this.randomSeed++);return t-Math.floor(t)}},{key:"positionInitially",value:function(t){if(!0!==this.options.hierarchical.enabled){this.randomSeed=this.initialRandomSeed;for(var e=t.length+50,i=0;i150){for(var s=t.length;t.length>150&&o<=10;){o+=1;var r=t.length;o%3==0?this.body.modules.clustering.clusterBridges(n):this.body.modules.clustering.clusterOutliers(n);if(r==t.length&&o%3!=0)return this._declusterAll(),this.body.emitter.emit("_layoutFailed"),void console.info("This network could not be positioned by this version of the improved layout algorithm. Please disable improvedLayout for better performance.")}this.body.modules.kamadaKawai.setOptions({springLength:Math.max(150,2*s)})}o>10&&console.info("The clustering didn't succeed within the amount of interations allowed, progressing with partial result."),this.body.modules.kamadaKawai.solve(t,this.body.edgeIndices,!0),this._shiftToCenter();for(var a=0;a0){var t=void 0,e=void 0,i=!1,o=!1;this.lastNodeOnLevel={},this.hierarchical=new b;for(e in this.body.nodes)this.body.nodes.hasOwnProperty(e)&&(t=this.body.nodes[e],void 0!==t.options.level?(i=!0,this.hierarchical.levels[e]=t.options.level):o=!0);if(!0===o&&!0===i)throw new Error("To use the hierarchical layout, nodes require either no predefined levels or levels have to be defined for all nodes.");if(!0===o){var n=this.options.hierarchical.sortMethod;"hubsize"===n?this._determineLevelsByHubsize():"directed"===n?this._determineLevelsDirected():"custom"===n&&this._determineLevelsCustomCallback()}for(var s in this.body.nodes)this.body.nodes.hasOwnProperty(s)&&this.hierarchical.ensureLevel(s);var r=this._getDistribution();this._generateMap(),this._placeNodesByHierarchy(r),this._condenseHierarchy(),this._shiftToCenter()}}},{key:"_condenseHierarchy",value:function(){var t=this,e=!1,i={},o=function(e,i){var o=t.hierarchical.trees;for(var n in o)o.hasOwnProperty(n)&&o[n]===e&&t.direction.shift(n,i)},n=function(){for(var e=[],i=0;i0)for(var s=0;s1&&void 0!==arguments[1]?arguments[1]:1e9,o=1e9,n=1e9,r=1e9,a=-1e9;for(var h in e)if(e.hasOwnProperty(h)){var d=t.body.nodes[h],l=t.hierarchical.levels[d.id],u=t.direction.getPosition(d),c=t._getSpaceAroundNode(d,e),p=(0,s.default)(c,2),f=p[0],m=p[1];o=Math.min(f,o),n=Math.min(m,n),l<=i&&(r=Math.min(u,r),a=Math.max(u,a))}return[r,a,o,n]},h=function(e,i){var o=t.hierarchical.getMaxLevel(e.id),n=t.hierarchical.getMaxLevel(i.id);return Math.min(o,n)},d=function(e,i,o){for(var n=t.hierarchical,s=0;s1)for(var h=0;h2&&void 0!==arguments[2]&&arguments[2],s=t.direction.getPosition(i),d=t.direction.getPosition(o),l=Math.abs(d-s),u=t.options.hierarchical.nodeSpacing;if(l>u){var c={},p={};r(i,c),r(o,p);var f=h(i,o),m=a(c,f),v=a(p,f),g=m[1],y=v[0],b=v[2];if(Math.abs(g-y)>u){var _=g-y+u;_<-b+u&&(_=-b+u),_<0&&(t._shiftBlock(o.id,_),e=!0,!0===n&&t._centerParent(o))}}},u=function(o,n){for(var h=n.id,d=n.edges,l=t.hierarchical.levels[n.id],u=t.options.hierarchical.levelSeparation*t.options.hierarchical.levelSeparation,c={},p=[],f=0;f0?p=Math.min(c,u-t.options.hierarchical.nodeSpacing):c<0&&(p=-Math.min(-c,l-t.options.hierarchical.nodeSpacing)),0!=p&&(t._shiftBlock(n.id,p),e=!0)}(_),_=b(o,d),function(i){var o=t.direction.getPosition(n),r=t._getSpaceAroundNode(n),a=(0,s.default)(r,2),h=a[0],d=a[1],l=i-o,u=o;l>0?u=Math.min(o+(d-t.options.hierarchical.nodeSpacing),i):l<0&&(u=Math.max(o-(h-t.options.hierarchical.nodeSpacing),i)),u!==o&&(t.direction.setPosition(n,u),e=!0)}(_)};!0===this.options.hierarchical.blockShifting&&(function(i){var o=t.hierarchical.getLevels();o=o.reverse();for(var n=0;n0&&Math.abs(p)0&&(a=this.direction.getPosition(i[n-1])+r),this.direction.setPosition(s,a,e),this._validatePositionAndContinue(s,e,a),o++}}}}},{key:"_placeBranchNodes",value:function(t,e){var i=this.hierarchical.childrenReference[t];if(void 0!==i){for(var o=[],n=0;ne&&void 0===this.positionedNodes[r.id]))return;var h=this.options.hierarchical.nodeSpacing,d=void 0;d=0===s?this.direction.getPosition(this.body.nodes[t]):this.direction.getPosition(o[s-1])+h,this.direction.setPosition(r,d,a),this._validatePositionAndContinue(r,a,d)}var l=this._getCenterPosition(o);this.direction.setPosition(this.body.nodes[t],l,e)}}},{key:"_validatePositionAndContinue",value:function(t,e,i){if(this.hierarchical.isTree){if(void 0!==this.lastNodeOnLevel[e]){var o=this.direction.getPosition(this.body.nodes[this.lastNodeOnLevel[e]]);if(i-ot.hierarchical.levels[e.id]&&t.hierarchical.addRelation(e.id,i.id)};this._crawlNetwork(e),this.hierarchical.checkIfTree()}},{key:"_crawlNetwork",value:function(){var t=this,e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:function(){},i=arguments[1],o={},n=function i(n,s){if(void 0===o[n.id]){t.hierarchical.setTreeIndex(n,s),o[n.id]=!0;for(var r=void 0,a=t._getActiveEdges(n),h=0;h2&&void 0!==arguments[2]?arguments[2]:void 0;this.fake_use(t,e,i),this.abstract()}},{key:"getTreeSize",value:function(t){return this.fake_use(t),this.abstract()}},{key:"sort",value:function(t){this.fake_use(t),this.abstract()}},{key:"fix",value:function(t,e){this.fake_use(t,e),this.abstract()}},{key:"shift",value:function(t,e){this.fake_use(t,e),this.abstract()}}]),t}(),m=function(t){function e(t){(0,u.default)(this,e);var i=(0,a.default)(this,(e.__proto__||(0,s.default)(e)).call(this));return i.layout=t,i}return(0,d.default)(e,t),(0,p.default)(e,[{key:"curveType",value:function(){return"horizontal"}},{key:"getPosition",value:function(t){return t.x}},{key:"setPosition",value:function(t,e){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:void 0;void 0!==i&&this.layout.hierarchical.addToOrdering(t,i),t.x=e}},{key:"getTreeSize",value:function(t){var e=this.layout.hierarchical.getTreeSize(this.layout.body.nodes,t);return{min:e.min_x,max:e.max_x}}},{key:"sort",value:function(t){t.sort(function(t,e){return void 0===t.x||void 0===e.x?0:t.x-e.x})}},{key:"fix",value:function(t,e){t.y=this.layout.options.hierarchical.levelSeparation*e,t.options.fixed.y=!0}},{key:"shift",value:function(t,e){this.layout.body.nodes[t].x+=e}}]),e}(f),v=function(t){function e(t){(0,u.default)(this,e);var i=(0,a.default)(this,(e.__proto__||(0,s.default)(e)).call(this));return i.layout=t,i}return(0,d.default)(e,t),(0,p.default)(e,[{key:"curveType",value:function(){return"vertical"}},{key:"getPosition",value:function(t){return t.y}},{key:"setPosition",value:function(t,e){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:void 0;void 0!==i&&this.layout.hierarchical.addToOrdering(t,i),t.y=e}},{key:"getTreeSize",value:function(t){var e=this.layout.hierarchical.getTreeSize(this.layout.body.nodes,t);return{min:e.min_y,max:e.max_y}}},{key:"sort",value:function(t){t.sort(function(t,e){return void 0===t.y||void 0===e.y?0:t.y-e.y})}},{key:"fix",value:function(t,e){t.x=this.layout.options.hierarchical.levelSeparation*e,t.options.fixed.x=!0}},{key:"shift",value:function(t,e){this.layout.body.nodes[t].y+=e}}]),e}(f);e.HorizontalStrategy=v,e.VerticalStrategy=m},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(8),s=o(n),r=i(19),a=o(r),h=i(6),d=o(h),l=i(0),u=o(l),c=i(1),p=o(c),f=i(2),m=i(10),v=i(37),g=function(){function t(e,i,o){var n=this;(0,u.default)(this,t),this.body=e,this.canvas=i,this.selectionHandler=o,this.editMode=!1,this.manipulationDiv=void 0,this.editModeDiv=void 0,this.closeDiv=void 0,this.manipulationHammers=[],this.temporaryUIFunctions={},this.temporaryEventFunctions=[],this.touchTime=0,this.temporaryIds={nodes:[],edges:[]},this.guiEnabled=!1,this.inMode=!1,this.selectedControlNode=void 0,this.options={},this.defaultOptions={enabled:!1,initiallyActive:!1,addNode:!0,addEdge:!0,editNode:void 0,editEdge:!0,deleteNode:!0,deleteEdge:!0,controlNodeStyle:{shape:"dot",size:6,color:{background:"#ff0000",border:"#3c3c3c",highlight:{background:"#07f968",border:"#3c3c3c"}},borderWidth:2,borderWidthSelected:2}},f.extend(this.options,this.defaultOptions),this.body.emitter.on("destroy",function(){n._clean()}),this.body.emitter.on("_dataChanged",this._restore.bind(this)),this.body.emitter.on("_resetData",this._restore.bind(this))}return(0,p.default)(t,[{key:"_restore",value:function(){!1!==this.inMode&&(!0===this.options.initiallyActive?this.enableEditMode():this.disableEditMode())}},{key:"setOptions",value:function(t,e,i){void 0!==e&&(void 0!==e.locale?this.options.locale=e.locale:this.options.locale=i.locale,void 0!==e.locales?this.options.locales=e.locales:this.options.locales=i.locales),void 0!==t&&("boolean"==typeof t?this.options.enabled=t:(this.options.enabled=!0,f.deepExtend(this.options,t)),!0===this.options.initiallyActive&&(this.editMode=!0),this._setup())}},{key:"toggleEditMode",value:function(){!0===this.editMode?this.disableEditMode():this.enableEditMode()}},{key:"enableEditMode",value:function(){this.editMode=!0,this._clean(),!0===this.guiEnabled&&(this.manipulationDiv.style.display="block",this.closeDiv.style.display="block",this.editModeDiv.style.display="none",this.showManipulatorToolbar())}},{key:"disableEditMode",value:function(){this.editMode=!1,this._clean(),!0===this.guiEnabled&&(this.manipulationDiv.style.display="none",this.closeDiv.style.display="none",this.editModeDiv.style.display="block",this._createEditButton())}},{key:"showManipulatorToolbar",value:function(){if(this._clean(),this.manipulationDOM={},!0===this.guiEnabled){this.editMode=!0,this.manipulationDiv.style.display="block",this.closeDiv.style.display="block";var t=this.selectionHandler._getSelectedNodeCount(),e=this.selectionHandler._getSelectedEdgeCount(),i=t+e,o=this.options.locales[this.options.locale],n=!1;!1!==this.options.addNode&&(this._createAddNodeButton(o),n=!0),!1!==this.options.addEdge&&(!0===n?this._createSeperator(1):n=!0,this._createAddEdgeButton(o)),1===t&&"function"==typeof this.options.editNode?(!0===n?this._createSeperator(2):n=!0,this._createEditNodeButton(o)):1===e&&0===t&&!1!==this.options.editEdge&&(!0===n?this._createSeperator(3):n=!0,this._createEditEdgeButton(o)),0!==i&&(t>0&&!1!==this.options.deleteNode?(!0===n&&this._createSeperator(4),this._createDeleteButton(o)):0===t&&!1!==this.options.deleteEdge&&(!0===n&&this._createSeperator(4),this._createDeleteButton(o))),this._bindHammerToDiv(this.closeDiv,this.toggleEditMode.bind(this)),this._temporaryBindEvent("select",this.showManipulatorToolbar.bind(this))}this.body.emitter.emit("_redraw")}},{key:"addNodeMode",value:function(){if(!0!==this.editMode&&this.enableEditMode(),this._clean(),this.inMode="addNode",!0===this.guiEnabled){var t=this.options.locales[this.options.locale];this.manipulationDOM={},this._createBackButton(t),this._createSeperator(),this._createDescription(t.addDescription||this.options.locales.en.addDescription),this._bindHammerToDiv(this.closeDiv,this.toggleEditMode.bind(this))}this._temporaryBindEvent("click",this._performAddNode.bind(this))}},{key:"editNode",value:function(){var t=this;!0!==this.editMode&&this.enableEditMode(),this._clean();var e=this.selectionHandler._getSelectedNode();if(void 0!==e){if(this.inMode="editNode","function"!=typeof this.options.editNode)throw new Error("No function has been configured to handle the editing of nodes.");if(!0!==e.isCluster){var i=f.deepExtend({},e.options,!1);if(i.x=e.x,i.y=e.y,2!==this.options.editNode.length)throw new Error("The function for edit does not support two arguments (data, callback)");this.options.editNode(i,function(e){null!==e&&void 0!==e&&"editNode"===t.inMode&&t.body.data.nodes.getDataSet().update(e),t.showManipulatorToolbar()})}else alert(this.options.locales[this.options.locale].editClusterError||this.options.locales.en.editClusterError)}else this.showManipulatorToolbar()}},{key:"addEdgeMode",value:function(){if(!0!==this.editMode&&this.enableEditMode(),this._clean(),this.inMode="addEdge",!0===this.guiEnabled){var t=this.options.locales[this.options.locale];this.manipulationDOM={},this._createBackButton(t),this._createSeperator(),this._createDescription(t.edgeDescription||this.options.locales.en.edgeDescription),this._bindHammerToDiv(this.closeDiv,this.toggleEditMode.bind(this))}this._temporaryBindUI("onTouch",this._handleConnect.bind(this)),this._temporaryBindUI("onDragEnd",this._finishConnect.bind(this)),this._temporaryBindUI("onDrag",this._dragControlNode.bind(this)),this._temporaryBindUI("onRelease",this._finishConnect.bind(this)),this._temporaryBindUI("onDragStart",this._dragStartEdge.bind(this)),this._temporaryBindUI("onHold",function(){})}},{key:"editEdgeMode",value:function(){if(!0!==this.editMode&&this.enableEditMode(),this._clean(),this.inMode="editEdge","object"===(0,d.default)(this.options.editEdge)&&"function"==typeof this.options.editEdge.editWithoutDrag&&(this.edgeBeingEditedId=this.selectionHandler.getSelectedEdges()[0],void 0!==this.edgeBeingEditedId)){var t=this.body.edges[this.edgeBeingEditedId];return void this._performEditEdge(t.from,t.to)}if(!0===this.guiEnabled){var e=this.options.locales[this.options.locale];this.manipulationDOM={},this._createBackButton(e),this._createSeperator(),this._createDescription(e.editEdgeDescription||this.options.locales.en.editEdgeDescription),this._bindHammerToDiv(this.closeDiv,this.toggleEditMode.bind(this))}if(this.edgeBeingEditedId=this.selectionHandler.getSelectedEdges()[0],void 0!==this.edgeBeingEditedId){var i=this.body.edges[this.edgeBeingEditedId],o=this._getNewTargetNode(i.from.x,i.from.y),n=this._getNewTargetNode(i.to.x,i.to.y);this.temporaryIds.nodes.push(o.id),this.temporaryIds.nodes.push(n.id),this.body.nodes[o.id]=o,this.body.nodeIndices.push(o.id),this.body.nodes[n.id]=n,this.body.nodeIndices.push(n.id),this._temporaryBindUI("onTouch",this._controlNodeTouch.bind(this)),this._temporaryBindUI("onTap",function(){}),this._temporaryBindUI("onHold",function(){}),this._temporaryBindUI("onDragStart",this._controlNodeDragStart.bind(this)),this._temporaryBindUI("onDrag",this._controlNodeDrag.bind(this)),this._temporaryBindUI("onDragEnd",this._controlNodeDragEnd.bind(this)),this._temporaryBindUI("onMouseMove",function(){}),this._temporaryBindEvent("beforeDrawing",function(t){var e=i.edgeType.findBorderPositions(t);!1===o.selected&&(o.x=e.from.x,o.y=e.from.y),!1===n.selected&&(n.x=e.to.x,n.y=e.to.y)}),this.body.emitter.emit("_redraw")}else this.showManipulatorToolbar()}},{key:"deleteSelected",value:function(){var t=this;!0!==this.editMode&&this.enableEditMode(),this._clean(),this.inMode="delete";var e=this.selectionHandler.getSelectedNodes(),i=this.selectionHandler.getSelectedEdges(),o=void 0;if(e.length>0){for(var n=0;n0&&"function"==typeof this.options.deleteEdge&&(o=this.options.deleteEdge);if("function"==typeof o){var s={nodes:e,edges:i};if(2!==o.length)throw new Error("The function for delete does not support two arguments (data, callback)");o(s,function(e){null!==e&&void 0!==e&&"delete"===t.inMode?(t.body.data.edges.getDataSet().remove(e.edges),t.body.data.nodes.getDataSet().remove(e.nodes),t.body.emitter.emit("startSimulation"),t.showManipulatorToolbar()):(t.body.emitter.emit("startSimulation"),t.showManipulatorToolbar())})}else this.body.data.edges.getDataSet().remove(i),this.body.data.nodes.getDataSet().remove(e),this.body.emitter.emit("startSimulation"),this.showManipulatorToolbar()}},{key:"_setup",value:function(){!0===this.options.enabled?(this.guiEnabled=!0,this._createWrappers(),!1===this.editMode?this._createEditButton():this.showManipulatorToolbar()):(this._removeManipulationDOM(),this.guiEnabled=!1)}},{key:"_createWrappers",value:function(){void 0===this.manipulationDiv&&(this.manipulationDiv=document.createElement("div"),this.manipulationDiv.className="vis-manipulation",!0===this.editMode?this.manipulationDiv.style.display="block":this.manipulationDiv.style.display="none",this.canvas.frame.appendChild(this.manipulationDiv)),void 0===this.editModeDiv&&(this.editModeDiv=document.createElement("div"),this.editModeDiv.className="vis-edit-mode",!0===this.editMode?this.editModeDiv.style.display="none":this.editModeDiv.style.display="block",this.canvas.frame.appendChild(this.editModeDiv)),void 0===this.closeDiv&&(this.closeDiv=document.createElement("div"),this.closeDiv.className="vis-close",this.closeDiv.style.display=this.manipulationDiv.style.display,this.canvas.frame.appendChild(this.closeDiv))}},{key:"_getNewTargetNode",value:function(t,e){var i=f.deepExtend({},this.options.controlNodeStyle);i.id="targetNode"+f.randomUUID(),i.hidden=!1,i.physics=!1,i.x=t,i.y=e;var o=this.body.functions.createNode(i);return o.shape.boundingBox={left:t,right:t,top:e,bottom:e},o}},{key:"_createEditButton",value:function(){this._clean(),this.manipulationDOM={},f.recursiveDOMDelete(this.editModeDiv);var t=this.options.locales[this.options.locale],e=this._createButton("editMode","vis-button vis-edit vis-edit-mode",t.edit||this.options.locales.en.edit);this.editModeDiv.appendChild(e),this._bindHammerToDiv(e,this.toggleEditMode.bind(this))}},{key:"_clean",value:function(){this.inMode=!1,!0===this.guiEnabled&&(f.recursiveDOMDelete(this.editModeDiv),f.recursiveDOMDelete(this.manipulationDiv),this._cleanManipulatorHammers()),this._cleanupTemporaryNodesAndEdges(),this._unbindTemporaryUIs(),this._unbindTemporaryEvents(),this.body.emitter.emit("restorePhysics")}},{key:"_cleanManipulatorHammers",value:function(){if(0!=this.manipulationHammers.length){for(var t=0;t0&&void 0!==arguments[0]?arguments[0]:1;this.manipulationDOM["seperatorLineDiv"+t]=document.createElement("div"),this.manipulationDOM["seperatorLineDiv"+t].className="vis-separator-line",this.manipulationDiv.appendChild(this.manipulationDOM["seperatorLineDiv"+t])}},{key:"_createAddNodeButton",value:function(t){var e=this._createButton("addNode","vis-button vis-add",t.addNode||this.options.locales.en.addNode);this.manipulationDiv.appendChild(e),this._bindHammerToDiv(e,this.addNodeMode.bind(this))}},{key:"_createAddEdgeButton",value:function(t){var e=this._createButton("addEdge","vis-button vis-connect",t.addEdge||this.options.locales.en.addEdge);this.manipulationDiv.appendChild(e),this._bindHammerToDiv(e,this.addEdgeMode.bind(this))}},{key:"_createEditNodeButton",value:function(t){var e=this._createButton("editNode","vis-button vis-edit",t.editNode||this.options.locales.en.editNode);this.manipulationDiv.appendChild(e),this._bindHammerToDiv(e,this.editNode.bind(this))}},{key:"_createEditEdgeButton",value:function(t){var e=this._createButton("editEdge","vis-button vis-edit",t.editEdge||this.options.locales.en.editEdge);this.manipulationDiv.appendChild(e),this._bindHammerToDiv(e,this.editEdgeMode.bind(this))}},{key:"_createDeleteButton",value:function(t){var e;e=this.options.rtl?"vis-button vis-delete-rtl":"vis-button vis-delete";var i=this._createButton("delete",e,t.del||this.options.locales.en.del);this.manipulationDiv.appendChild(i),this._bindHammerToDiv(i,this.deleteSelected.bind(this))}},{key:"_createBackButton",value:function(t){var e=this._createButton("back","vis-button vis-back",t.back||this.options.locales.en.back);this.manipulationDiv.appendChild(e),this._bindHammerToDiv(e,this.showManipulatorToolbar.bind(this))}},{key:"_createButton",value:function(t,e,i){var o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"vis-label";return this.manipulationDOM[t+"Div"]=document.createElement("div"),this.manipulationDOM[t+"Div"].className=e,this.manipulationDOM[t+"Label"]=document.createElement("div"),this.manipulationDOM[t+"Label"].className=o,this.manipulationDOM[t+"Label"].innerHTML=i,this.manipulationDOM[t+"Div"].appendChild(this.manipulationDOM[t+"Label"]),this.manipulationDOM[t+"Div"]}},{key:"_createDescription",value:function(t){this.manipulationDiv.appendChild(this._createButton("description","vis-button vis-none",t))}},{key:"_temporaryBindEvent",value:function(t,e){this.temporaryEventFunctions.push({event:t,boundFunction:e}),this.body.emitter.on(t,e)}},{key:"_temporaryBindUI",value:function(t,e){if(void 0===this.body.eventListeners[t])throw new Error("This UI function does not exist. Typo? You tried: "+t+" possible are: "+(0,a.default)((0,s.default)(this.body.eventListeners)));this.temporaryUIFunctions[t]=this.body.eventListeners[t],this.body.eventListeners[t]=e}},{key:"_unbindTemporaryUIs",value:function(){for(var t in this.temporaryUIFunctions)this.temporaryUIFunctions.hasOwnProperty(t)&&(this.body.eventListeners[t]=this.temporaryUIFunctions[t],delete this.temporaryUIFunctions[t]);this.temporaryUIFunctions={}}},{key:"_unbindTemporaryEvents",value:function(){for(var t=0;t=0;r--)if(n[r]!==this.selectedControlNode.id){s=this.body.nodes[n[r]];break}if(void 0!==s&&void 0!==this.selectedControlNode)if(!0===s.isCluster)alert(this.options.locales[this.options.locale].createEdgeError||this.options.locales.en.createEdgeError);else{var a=this.body.nodes[this.temporaryIds.nodes[0]];this.selectedControlNode.id===a.id?this._performEditEdge(s.id,o.to.id):this._performEditEdge(o.from.id,s.id)}else o.updateEdgeType(),this.body.emitter.emit("restorePhysics");this.body.emitter.emit("_redraw")}}},{key:"_handleConnect",value:function(t){if((new Date).valueOf()-this.touchTime>100){this.lastTouch=this.body.functions.getPointer(t.center),this.lastTouch.translation=f.extend({},this.body.view.translation);var e=this.lastTouch,i=this.selectionHandler.getNodeAt(e);if(void 0!==i)if(!0===i.isCluster)alert(this.options.locales[this.options.locale].createEdgeError||this.options.locales.en.createEdgeError);else{var o=this._getNewTargetNode(i.x,i.y);this.body.nodes[o.id]=o,this.body.nodeIndices.push(o.id);var n=this.body.functions.createEdge({id:"connectionEdge"+f.randomUUID(),from:i.id,to:o.id,physics:!1,smooth:{enabled:!0,type:"continuous",roundness:.5}});this.body.edges[n.id]=n,this.body.edgeIndices.push(n.id),this.temporaryIds.nodes.push(o.id),this.temporaryIds.edges.push(n.id)}this.touchTime=(new Date).valueOf()}}},{key:"_dragControlNode",value:function(t){var e=this.body.functions.getPointer(t.center);if(void 0!==this.temporaryIds.nodes[0]){var i=this.body.nodes[this.temporaryIds.nodes[0]];i.x=this.canvas._XconvertDOMtoCanvas(e.x),i.y=this.canvas._YconvertDOMtoCanvas(e.y),this.body.emitter.emit("_redraw")}else{var o=e.x-this.lastTouch.x,n=e.y-this.lastTouch.y;this.body.view.translation={x:this.lastTouch.translation.x+o,y:this.lastTouch.translation.y+n}}}},{key:"_finishConnect",value:function(t){var e=this.body.functions.getPointer(t.center),i=this.selectionHandler._pointerToPositionObject(e),o=void 0;void 0!==this.temporaryIds.edges[0]&&(o=this.body.edges[this.temporaryIds.edges[0]].fromId);for(var n=this.selectionHandler._getAllNodesOverlappingWith(i),s=void 0,r=n.length-1;r>=0;r--)if(-1===this.temporaryIds.nodes.indexOf(n[r])){s=this.body.nodes[n[r]];break}this._cleanupTemporaryNodesAndEdges(),void 0!==s&&(!0===s.isCluster?alert(this.options.locales[this.options.locale].createEdgeError||this.options.locales.en.createEdgeError):void 0!==this.body.nodes[o]&&void 0!==this.body.nodes[s.id]&&this._performAddEdge(o,s.id)),this.body.emitter.emit("_redraw")}},{key:"_dragStartEdge",value:function(t){var e=this.lastTouch;this.selectionHandler._generateClickEvent("dragStart",t,e,void 0,!0)}},{key:"_performAddNode",value:function(t){var e=this,i={id:f.randomUUID(),x:t.pointer.canvas.x,y:t.pointer.canvas.y,label:"new"};if("function"==typeof this.options.addNode){if(2!==this.options.addNode.length)throw this.showManipulatorToolbar(),new Error("The function for add does not support two arguments (data,callback)");this.options.addNode(i,function(t){null!==t&&void 0!==t&&"addNode"===e.inMode&&(e.body.data.nodes.getDataSet().add(t),e.showManipulatorToolbar())})}else this.body.data.nodes.getDataSet().add(i),this.showManipulatorToolbar()}},{key:"_performAddEdge",value:function(t,e){var i=this,o={from:t,to:e};if("function"==typeof this.options.addEdge){if(2!==this.options.addEdge.length)throw new Error("The function for connect does not support two arguments (data,callback)");this.options.addEdge(o,function(t){null!==t&&void 0!==t&&"addEdge"===i.inMode&&(i.body.data.edges.getDataSet().add(t),i.selectionHandler.unselectAll(),i.showManipulatorToolbar())})}else this.body.data.edges.getDataSet().add(o),this.selectionHandler.unselectAll(),this.showManipulatorToolbar()}},{key:"_performEditEdge",value:function(t,e){var i=this,o={id:this.edgeBeingEditedId,from:t,to:e,label:this.body.data.edges._data[this.edgeBeingEditedId].label},n=this.options.editEdge;if("object"===(void 0===n?"undefined":(0,d.default)(n))&&(n=n.editWithoutDrag),"function"==typeof n){if(2!==n.length)throw new Error("The function for edit does not support two arguments (data, callback)");n(o,function(t){null===t||void 0===t||"editEdge"!==i.inMode?(i.body.edges[o.id].updateEdgeType(),i.body.emitter.emit("_redraw"),i.showManipulatorToolbar()):(i.body.data.edges.getDataSet().update(t),i.selectionHandler.unselectAll(),i.showManipulatorToolbar())})}else this.body.data.edges.getDataSet().update(o),this.selectionHandler.unselectAll(),this.showManipulatorToolbar()}}]),t}();e.default=g},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(30),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(239),u=o(l),c=function(){function t(e,i,o){(0,a.default)(this,t),this.body=e,this.springLength=i,this.springConstant=o,this.distanceSolver=new u.default}return(0,d.default)(t,[{key:"setOptions",value:function(t){t&&(t.springLength&&(this.springLength=t.springLength),t.springConstant&&(this.springConstant=t.springConstant))}},{key:"solve",value:function(t,e){var i=arguments.length>2&&void 0!==arguments[2]&&arguments[2],o=this.distanceSolver.getDistances(this.body,t,e);this._createL_matrix(o),this._createK_matrix(o),this._createE_matrix();for(var n=0,r=Math.max(1e3,Math.min(10*this.body.nodeIndices.length,6e3)),a=1e9,h=0,d=0,l=0,u=0,c=0;a>.01&&n1&&c<5;){c+=1,this._moveNode(h,d,l);var m=this._getEnergy(h),v=(0,s.default)(m,3);u=v[0],d=v[1],l=v[2]}}}},{key:"_getHighestEnergyNode",value:function(t){for(var e=this.body.nodeIndices,i=this.body.nodes,o=0,n=e[0],r=0,a=0,h=0;h0&&"function"==typeof this.options.deleteEdge&&(o=this.options.deleteEdge);if("function"==typeof o){var s={nodes:e,edges:i};if(2!==o.length)throw new Error("The function for delete does not support two arguments (data, callback)");o(s,function(e){null!==e&&void 0!==e&&"delete"===t.inMode?(t.body.data.edges.getDataSet().remove(e.edges),t.body.data.nodes.getDataSet().remove(e.nodes),t.body.emitter.emit("startSimulation"),t.showManipulatorToolbar()):(t.body.emitter.emit("startSimulation"),t.showManipulatorToolbar())})}else this.body.data.edges.getDataSet().remove(i),this.body.data.nodes.getDataSet().remove(e),this.body.emitter.emit("startSimulation"),this.showManipulatorToolbar()}},{key:"_setup",value:function(){!0===this.options.enabled?(this.guiEnabled=!0,this._createWrappers(),!1===this.editMode?this._createEditButton():this.showManipulatorToolbar()):(this._removeManipulationDOM(),this.guiEnabled=!1)}},{key:"_createWrappers",value:function(){void 0===this.manipulationDiv&&(this.manipulationDiv=document.createElement("div"),this.manipulationDiv.className="vis-manipulation",!0===this.editMode?this.manipulationDiv.style.display="block":this.manipulationDiv.style.display="none",this.canvas.frame.appendChild(this.manipulationDiv)),void 0===this.editModeDiv&&(this.editModeDiv=document.createElement("div"),this.editModeDiv.className="vis-edit-mode",!0===this.editMode?this.editModeDiv.style.display="none":this.editModeDiv.style.display="block",this.canvas.frame.appendChild(this.editModeDiv)),void 0===this.closeDiv&&(this.closeDiv=document.createElement("div"),this.closeDiv.className="vis-close",this.closeDiv.style.display=this.manipulationDiv.style.display,this.canvas.frame.appendChild(this.closeDiv))}},{key:"_getNewTargetNode",value:function(t,e){var i=f.deepExtend({},this.options.controlNodeStyle);i.id="targetNode"+f.randomUUID(),i.hidden=!1,i.physics=!1,i.x=t,i.y=e;var o=this.body.functions.createNode(i);return o.shape.boundingBox={left:t,right:t,top:e,bottom:e},o}},{key:"_createEditButton",value:function(){this._clean(),this.manipulationDOM={},f.recursiveDOMDelete(this.editModeDiv);var t=this.options.locales[this.options.locale],e=this._createButton("editMode","vis-button vis-edit vis-edit-mode",t.edit||this.options.locales.en.edit);this.editModeDiv.appendChild(e),this._bindHammerToDiv(e,this.toggleEditMode.bind(this))}},{key:"_clean",value:function(){this.inMode=!1,!0===this.guiEnabled&&(f.recursiveDOMDelete(this.editModeDiv),f.recursiveDOMDelete(this.manipulationDiv),this._cleanManipulatorHammers()),this._cleanupTemporaryNodesAndEdges(),this._unbindTemporaryUIs(),this._unbindTemporaryEvents(),this.body.emitter.emit("restorePhysics")}},{key:"_cleanManipulatorHammers",value:function(){if(0!=this.manipulationHammers.length){for(var t=0;t0&&void 0!==arguments[0]?arguments[0]:1;this.manipulationDOM["seperatorLineDiv"+t]=document.createElement("div"),this.manipulationDOM["seperatorLineDiv"+t].className="vis-separator-line",this.manipulationDiv.appendChild(this.manipulationDOM["seperatorLineDiv"+t])}},{key:"_createAddNodeButton",value:function(t){var e=this._createButton("addNode","vis-button vis-add",t.addNode||this.options.locales.en.addNode);this.manipulationDiv.appendChild(e),this._bindHammerToDiv(e,this.addNodeMode.bind(this))}},{key:"_createAddEdgeButton",value:function(t){var e=this._createButton("addEdge","vis-button vis-connect",t.addEdge||this.options.locales.en.addEdge);this.manipulationDiv.appendChild(e),this._bindHammerToDiv(e,this.addEdgeMode.bind(this))}},{key:"_createEditNodeButton",value:function(t){var e=this._createButton("editNode","vis-button vis-edit",t.editNode||this.options.locales.en.editNode);this.manipulationDiv.appendChild(e),this._bindHammerToDiv(e,this.editNode.bind(this))}},{key:"_createEditEdgeButton",value:function(t){var e=this._createButton("editEdge","vis-button vis-edit",t.editEdge||this.options.locales.en.editEdge);this.manipulationDiv.appendChild(e),this._bindHammerToDiv(e,this.editEdgeMode.bind(this))}},{key:"_createDeleteButton",value:function(t){var e;e=this.options.rtl?"vis-button vis-delete-rtl":"vis-button vis-delete";var i=this._createButton("delete",e,t.del||this.options.locales.en.del);this.manipulationDiv.appendChild(i),this._bindHammerToDiv(i,this.deleteSelected.bind(this))}},{key:"_createBackButton",value:function(t){var e=this._createButton("back","vis-button vis-back",t.back||this.options.locales.en.back);this.manipulationDiv.appendChild(e),this._bindHammerToDiv(e,this.showManipulatorToolbar.bind(this))}},{key:"_createButton",value:function(t,e,i){var o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"vis-label";return this.manipulationDOM[t+"Div"]=document.createElement("div"),this.manipulationDOM[t+"Div"].className=e,this.manipulationDOM[t+"Label"]=document.createElement("div"),this.manipulationDOM[t+"Label"].className=o,this.manipulationDOM[t+"Label"].innerHTML=i,this.manipulationDOM[t+"Div"].appendChild(this.manipulationDOM[t+"Label"]),this.manipulationDOM[t+"Div"]}},{key:"_createDescription",value:function(t){this.manipulationDiv.appendChild(this._createButton("description","vis-button vis-none",t))}},{key:"_temporaryBindEvent",value:function(t,e){this.temporaryEventFunctions.push({event:t,boundFunction:e}),this.body.emitter.on(t,e)}},{key:"_temporaryBindUI",value:function(t,e){if(void 0===this.body.eventListeners[t])throw new Error("This UI function does not exist. Typo? You tried: "+t+" possible are: "+(0,a.default)((0,s.default)(this.body.eventListeners)));this.temporaryUIFunctions[t]=this.body.eventListeners[t],this.body.eventListeners[t]=e}},{key:"_unbindTemporaryUIs",value:function(){for(var t in this.temporaryUIFunctions)this.temporaryUIFunctions.hasOwnProperty(t)&&(this.body.eventListeners[t]=this.temporaryUIFunctions[t],delete this.temporaryUIFunctions[t]);this.temporaryUIFunctions={}}},{key:"_unbindTemporaryEvents",value:function(){for(var t=0;t=0;r--)if(n[r]!==this.selectedControlNode.id){s=this.body.nodes[n[r]];break}if(void 0!==s&&void 0!==this.selectedControlNode)if(!0===s.isCluster)alert(this.options.locales[this.options.locale].createEdgeError||this.options.locales.en.createEdgeError);else{var a=this.body.nodes[this.temporaryIds.nodes[0]];this.selectedControlNode.id===a.id?this._performEditEdge(s.id,o.to.id):this._performEditEdge(o.from.id,s.id)}else o.updateEdgeType(),this.body.emitter.emit("restorePhysics");this.body.emitter.emit("_redraw")}}},{key:"_handleConnect",value:function(t){if((new Date).valueOf()-this.touchTime>100){this.lastTouch=this.body.functions.getPointer(t.center),this.lastTouch.translation=f.extend({},this.body.view.translation);var e=this.lastTouch,i=this.selectionHandler.getNodeAt(e);if(void 0!==i)if(!0===i.isCluster)alert(this.options.locales[this.options.locale].createEdgeError||this.options.locales.en.createEdgeError);else{var o=this._getNewTargetNode(i.x,i.y);this.body.nodes[o.id]=o,this.body.nodeIndices.push(o.id);var n=this.body.functions.createEdge({id:"connectionEdge"+f.randomUUID(),from:i.id,to:o.id,physics:!1,smooth:{enabled:!0,type:"continuous",roundness:.5}});this.body.edges[n.id]=n,this.body.edgeIndices.push(n.id),this.temporaryIds.nodes.push(o.id),this.temporaryIds.edges.push(n.id)}this.touchTime=(new Date).valueOf()}}},{key:"_dragControlNode",value:function(t){var e=this.body.functions.getPointer(t.center);if(void 0!==this.temporaryIds.nodes[0]){var i=this.body.nodes[this.temporaryIds.nodes[0]];i.x=this.canvas._XconvertDOMtoCanvas(e.x),i.y=this.canvas._YconvertDOMtoCanvas(e.y),this.body.emitter.emit("_redraw")}else{var o=e.x-this.lastTouch.x,n=e.y-this.lastTouch.y;this.body.view.translation={x:this.lastTouch.translation.x+o,y:this.lastTouch.translation.y+n}}}},{key:"_finishConnect",value:function(t){var e=this.body.functions.getPointer(t.center),i=this.selectionHandler._pointerToPositionObject(e),o=void 0;void 0!==this.temporaryIds.edges[0]&&(o=this.body.edges[this.temporaryIds.edges[0]].fromId);for(var n=this.selectionHandler._getAllNodesOverlappingWith(i),s=void 0,r=n.length-1;r>=0;r--)if(-1===this.temporaryIds.nodes.indexOf(n[r])){s=this.body.nodes[n[r]];break}this._cleanupTemporaryNodesAndEdges(),void 0!==s&&(!0===s.isCluster?alert(this.options.locales[this.options.locale].createEdgeError||this.options.locales.en.createEdgeError):void 0!==this.body.nodes[o]&&void 0!==this.body.nodes[s.id]&&this._performAddEdge(o,s.id)),this.body.emitter.emit("_redraw")}},{key:"_dragStartEdge",value:function(t){var e=this.lastTouch;this.selectionHandler._generateClickEvent("dragStart",t,e,void 0,!0)}},{key:"_performAddNode",value:function(t){var e=this,i={id:f.randomUUID(),x:t.pointer.canvas.x,y:t.pointer.canvas.y,label:"new"};if("function"==typeof this.options.addNode){if(2!==this.options.addNode.length)throw this.showManipulatorToolbar(),new Error("The function for add does not support two arguments (data,callback)");this.options.addNode(i,function(t){null!==t&&void 0!==t&&"addNode"===e.inMode&&(e.body.data.nodes.getDataSet().add(t),e.showManipulatorToolbar())})}else this.body.data.nodes.getDataSet().add(i),this.showManipulatorToolbar()}},{key:"_performAddEdge",value:function(t,e){var i=this,o={from:t,to:e};if("function"==typeof this.options.addEdge){if(2!==this.options.addEdge.length)throw new Error("The function for connect does not support two arguments (data,callback)");this.options.addEdge(o,function(t){null!==t&&void 0!==t&&"addEdge"===i.inMode&&(i.body.data.edges.getDataSet().add(t),i.selectionHandler.unselectAll(),i.showManipulatorToolbar())})}else this.body.data.edges.getDataSet().add(o),this.selectionHandler.unselectAll(),this.showManipulatorToolbar()}},{key:"_performEditEdge",value:function(t,e){var i=this,o={id:this.edgeBeingEditedId,from:t,to:e,label:this.body.data.edges._data[this.edgeBeingEditedId].label},n=this.options.editEdge;if("object"===(void 0===n?"undefined":(0,d.default)(n))&&(n=n.editWithoutDrag),"function"==typeof n){if(2!==n.length)throw new Error("The function for edit does not support two arguments (data, callback)");n(o,function(t){null===t||void 0===t||"editEdge"!==i.inMode?(i.body.edges[o.id].updateEdgeType(),i.body.emitter.emit("_redraw"),i.showManipulatorToolbar()):(i.body.data.edges.getDataSet().update(t),i.selectionHandler.unselectAll(),i.showManipulatorToolbar())})}else this.body.data.edges.getDataSet().update(o),this.selectionHandler.unselectAll(),this.showManipulatorToolbar()}}]),t}();e.default=g},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(30),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(239),u=o(l),c=function(){function t(e,i,o){(0,a.default)(this,t),this.body=e,this.springLength=i,this.springConstant=o,this.distanceSolver=new u.default}return(0,d.default)(t,[{key:"setOptions",value:function(t){t&&(t.springLength&&(this.springLength=t.springLength),t.springConstant&&(this.springConstant=t.springConstant))}},{key:"solve",value:function(t,e){var i=arguments.length>2&&void 0!==arguments[2]&&arguments[2],o=this.distanceSolver.getDistances(this.body,t,e);this._createL_matrix(o),this._createK_matrix(o),this._createE_matrix();for(var n=0,r=Math.max(1e3,Math.min(10*this.body.nodeIndices.length,6e3)),a=1e9,h=0,d=0,l=0,u=0,c=0;a>.01&&n1&&c<5;){c+=1,this._moveNode(h,d,l);var m=this._getEnergy(h),v=(0,s.default)(m,3);u=v[0],d=v[1],l=v[2]}}}},{key:"_getHighestEnergyNode",value:function(t){for(var e=this.body.nodeIndices,i=this.body.nodes,o=0,n=e[0],r=0,a=0,h=0;h results = asList(pending, passed, skipped, failed, ambiguous, undefined); - - sort(results, SEVERITY); - - assertThat(results, equalTo(asList(passed, skipped, pending, undefined, ambiguous, failed))); - } - - @Test - public void passed_result_is_always_ok() { - Result passedResult = new Result(PASSED, 0L, null); - - assertTrue(passedResult.isOk(isStrict(false))); - assertTrue(passedResult.isOk(isStrict(true))); - } - - @Test - public void skipped_result_is_always_ok() { - Result skippedResult = new Result(SKIPPED, 0L, null); - - assertTrue(skippedResult.isOk(isStrict(false))); - assertTrue(skippedResult.isOk(isStrict(true))); - } - - @Test - public void failed_result_is_never_ok() { - Result failedResult = new Result(FAILED, 0L, null); - - assertFalse(failedResult.isOk(isStrict(false))); - assertFalse(failedResult.isOk(isStrict(true))); - } - - @Test - public void undefined_result_is_only_ok_when_not_strict() { - Result undefinedResult = new Result(UNDEFINED, 0L, null); - - assertTrue(undefinedResult.isOk(isStrict(false))); - assertFalse(undefinedResult.isOk(isStrict(true))); - } - - @Test - public void pending_result_is_only_ok_when_not_strict() { - Result pendingResult = new Result(PENDING, 0L, null); - - assertTrue(pendingResult.isOk(isStrict(false))); - assertFalse(pendingResult.isOk(isStrict(true))); - } - - @Test - public void is_query_returns_true_for_the_status_of_the_result_object() { - int checkCount = 0; - for (Result.Type status : Result.Type.values()) { - Result result = new Result(status, 0L, null); - - assertTrue(result.is(result.getStatus())); - checkCount += 1; - } - assertTrue("No checks performed", checkCount > 0); - } - - @Test - public void is_query_returns_false_for_statuses_different_from_the_status_of_the_result_object() { - int checkCount = 0; - for (Result.Type resultStatus : Result.Type.values()) { - Result result = new Result(resultStatus, 0L, null); - for (Result.Type status : Result.Type.values()) { - if (status != resultStatus) { - assertFalse(result.is(status)); - checkCount += 1; - } - } - } - assertTrue("No checks performed", checkCount > 0); - } - - private boolean isStrict(boolean value) { - return value; - } -} diff --git a/core/src/test/java/cucumber/runner/GlueTest.java b/core/src/test/java/cucumber/runner/GlueTest.java deleted file mode 100644 index 106ac97137..0000000000 --- a/core/src/test/java/cucumber/runner/GlueTest.java +++ /dev/null @@ -1,284 +0,0 @@ -package cucumber.runner; - -import cucumber.runtime.DuplicateStepDefinitionException; -import cucumber.runtime.HookDefinition; -import cucumber.runtime.StepDefinition; -import gherkin.pickles.Argument; -import gherkin.pickles.PickleCell; -import gherkin.pickles.PickleLocation; -import gherkin.pickles.PickleRow; -import gherkin.pickles.PickleStep; -import gherkin.pickles.PickleString; -import gherkin.pickles.PickleTable; -import io.cucumber.datatable.DataTable; -import io.cucumber.stepexpression.ArgumentMatcher; -import io.cucumber.stepexpression.ExpressionArgumentMatcher; -import io.cucumber.stepexpression.StepExpression; -import io.cucumber.stepexpression.StepExpressionFactory; -import io.cucumber.stepexpression.TypeRegistry; -import org.junit.Before; -import org.junit.Test; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -import java.util.Collections; - -import static java.util.Collections.singletonList; -import static java.util.Locale.ENGLISH; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class GlueTest { - - private Glue glue; - - @Before - public void setUp() { - glue = new Glue(mock(EventBus.class)); - } - - @Test - public void throws_duplicate_error_on_dupe_stepdefs() { - Glue glue = new Glue(mock(EventBus.class)); - - StepDefinition a = mock(StepDefinition.class); - when(a.getPattern()).thenReturn("hello"); - when(a.getLocation(true)).thenReturn("foo.bf:10"); - glue.addStepDefinition(a); - - StepDefinition b = mock(StepDefinition.class); - when(b.getPattern()).thenReturn("hello"); - when(b.getLocation(true)).thenReturn("bar.bf:90"); - try { - glue.addStepDefinition(b); - fail("should have failed"); - } catch (DuplicateStepDefinitionException expected) { - assertEquals("Duplicate step definitions in foo.bf:10 and bar.bf:90", expected.getMessage()); - } - } - - @Test - public void removes_glue_that_is_scenario_scoped() { - // This test is a bit fragile - it is testing state, not behaviour. - // But it was too much hassle creating a better test without refactoring RuntimeGlue - // and probably some of its immediate collaborators... Aslak. - - StepDefinition sd = mock(StepDefinition.class); - when(sd.isScenarioScoped()).thenReturn(true); - when(sd.getPattern()).thenReturn("pattern"); - glue.addStepDefinition(sd); - - HookDefinition bh = mock(HookDefinition.class); - when(bh.isScenarioScoped()).thenReturn(true); - glue.addBeforeHook(bh); - - HookDefinition ah = mock(HookDefinition.class); - when(ah.isScenarioScoped()).thenReturn(true); - glue.addAfterHook(ah); - - assertEquals(1, glue.stepDefinitionsByPattern.size()); - assertEquals(1, glue.beforeHooks.size()); - assertEquals(1, glue.afterHooks.size()); - - glue.removeScenarioScopedGlue(); - - assertEquals(0, glue.stepDefinitionsByPattern.size()); - assertEquals(0, glue.beforeHooks.size()); - assertEquals(0, glue.afterHooks.size()); - } - - @Test - public void removes_scenario_scoped_cache_entries() { - StepDefinition sd = getStepDefinitionMockWithPattern("pattern"); - when(sd.isScenarioScoped()).thenReturn(true); - glue.addStepDefinition(sd); - String featurePath = "someFeature.feature"; - - String stepText = "pattern"; - PickleStep pickleStep1 = getPickleStep(stepText); - assertEquals(sd, glue.stepDefinitionMatch(featurePath, pickleStep1).getStepDefinition()); - - assertEquals(1, glue.stepDefinitionsByStepText.size()); - - glue.removeScenarioScopedGlue(); - - assertEquals(0, glue.stepDefinitionsByStepText.size()); - } - - @Test - public void returns_null_if_no_matching_steps_found() { - StepDefinition stepDefinition = getStepDefinitionMockWithPattern("pattern1"); - glue.addStepDefinition(stepDefinition); - String featurePath = "someFeature.feature"; - - PickleStep pickleStep = getPickleStep("pattern"); - assertNull(glue.stepDefinitionMatch(featurePath, pickleStep)); - verify(stepDefinition).matchedArguments(pickleStep); - } - - @Test - public void returns_match_from_cache_if_single_found() { - StepDefinition stepDefinition1 = getStepDefinitionMockWithPattern("^pattern1"); - StepDefinition stepDefinition2 = getStepDefinitionMockWithPattern("^pattern2"); - glue.addStepDefinition(stepDefinition1); - glue.addStepDefinition(stepDefinition2); - String featurePath = "someFeature.feature"; - String stepText = "pattern1"; - - PickleStep pickleStep1 = getPickleStep(stepText); - assertEquals(stepDefinition1, glue.stepDefinitionMatch(featurePath, pickleStep1).getStepDefinition()); - //verify if all defs are checked - verify(stepDefinition1).matchedArguments(pickleStep1); - verify(stepDefinition2).matchedArguments(pickleStep1); - - //check cache - StepDefinition entry = glue.stepDefinitionsByStepText.get(stepText); - assertEquals(stepDefinition1,entry); - - PickleStep pickleStep2 = getPickleStep(stepText); - assertEquals(stepDefinition1, glue.stepDefinitionMatch(featurePath, pickleStep2).getStepDefinition()); - //verify that only cached step definition has called matchedArguments again - verify(stepDefinition1,times(2)).matchedArguments(any(PickleStep.class)); - verify(stepDefinition2).matchedArguments(any(PickleStep.class)); - - } - - @Test - public void returns_match_from_cache_for_step_with_table() { - StepDefinition stepDefinition1 = getStepDefinitionMockWithPattern("^pattern1"); - StepDefinition stepDefinition2 = getStepDefinitionMockWithPattern("^pattern2"); - glue.addStepDefinition(stepDefinition1); - glue.addStepDefinition(stepDefinition2); - String featurePath = "someFeature.feature"; - String stepText = "pattern1"; - - PickleStep pickleStep1 = getPickleStepWithSingleCellTable(stepText, "cell 1"); - - PickleStepDefinitionMatch match1 = glue.stepDefinitionMatch(featurePath, pickleStep1); - - assertEquals(stepDefinition1, match1.getStepDefinition()); - //verify if all defs are checked - verify(stepDefinition1).matchedArguments(pickleStep1); - verify(stepDefinition2).matchedArguments(pickleStep1); - - //check cache - StepDefinition entry = glue.stepDefinitionsByStepText.get(stepText); - assertEquals(stepDefinition1,entry); - - //check arguments - assertEquals("cell 1", ((DataTable) match1.getArguments().get(0).getValue()).cell(0,0)); - - //check second match - PickleStep pickleStep2 = getPickleStepWithSingleCellTable(stepText, "cell 2"); - PickleStepDefinitionMatch match2 = glue.stepDefinitionMatch(featurePath, pickleStep2); - - //verify that only cached step definition has called matchedArguments again - verify(stepDefinition1,times(2)).matchedArguments(any(PickleStep.class)); - verify(stepDefinition2).matchedArguments(any(PickleStep.class)); - - //check arguments - assertEquals("cell 2",((DataTable) match2.getArguments().get(0).getValue()).cell(0,0)); - - - } - - @Test - public void returns_match_from_cache_for_ste_with_doc_string() { - StepDefinition stepDefinition1 = getStepDefinitionMockWithPattern("^pattern1"); - StepDefinition stepDefinition2 = getStepDefinitionMockWithPattern("^pattern2"); - glue.addStepDefinition(stepDefinition1); - glue.addStepDefinition(stepDefinition2); - String featurePath = "someFeature.feature"; - String stepText = "pattern1"; - - PickleStep pickleStep1 = getPickleStepWithDocString(stepText, "doc string 1"); - - PickleStepDefinitionMatch match1 = glue.stepDefinitionMatch(featurePath, pickleStep1); - - assertEquals(stepDefinition1, match1.getStepDefinition()); - //verify if all defs are checked - verify(stepDefinition1).matchedArguments(pickleStep1); - verify(stepDefinition2).matchedArguments(pickleStep1); - - //check cache - StepDefinition entry = glue.stepDefinitionsByStepText.get(stepText); - assertEquals(stepDefinition1,entry); - - //check arguments - assertEquals("doc string 1", match1.getArguments().get(0).getValue()); - - //check second match - PickleStep pickleStep2 = getPickleStepWithDocString(stepText, "doc string 2"); - PickleStepDefinitionMatch match2 = glue.stepDefinitionMatch(featurePath, pickleStep2); - - //verify that only cached step definition has called matchedArguments again - verify(stepDefinition1,times(2)).matchedArguments(any(PickleStep.class)); - verify(stepDefinition2).matchedArguments(any(PickleStep.class)); - - //check arguments - assertEquals("doc string 2",match2.getArguments().get(0).getValue()); - - - } - - - private static PickleStep getPickleStepWithSingleCellTable(String stepText, String cell) { - return new PickleStep(stepText, Collections.singletonList(new PickleTable(singletonList(new PickleRow(singletonList(new PickleCell(mock(PickleLocation.class), cell)))))), Collections.emptyList()); - } - - private static PickleStep getPickleStepWithDocString(String stepText, String doc) { - return new PickleStep(stepText, Collections.singletonList(new PickleString(mock(PickleLocation.class),doc)), Collections.emptyList()); - } - - @Test - public void throws_ambiguous_steps_def_exception_when_many_patterns_match() { - StepDefinition stepDefinition1 = getStepDefinitionMockWithPattern("pattern1"); - StepDefinition stepDefinition2 = getStepDefinitionMockWithPattern("^pattern2"); - StepDefinition stepDefinition3 = getStepDefinitionMockWithPattern("^pattern[1,3]"); - glue.addStepDefinition(stepDefinition1); - glue.addStepDefinition(stepDefinition2); - glue.addStepDefinition(stepDefinition3); - String featurePath = "someFeature.feature"; - - checkAmbiguousCalled(featurePath); - //try again to verify if we don't cache when there is ambiguous step - checkAmbiguousCalled(featurePath); - } - - private void checkAmbiguousCalled(String featurePath) { - boolean ambiguousCalled = false; - try { - - glue.stepDefinitionMatch(featurePath, getPickleStep("pattern1")); - } catch (AmbiguousStepDefinitionsException e) { - assertEquals(2,e.getMatches().size()); - ambiguousCalled = true; - } - assertTrue(ambiguousCalled); - } - - private static PickleStep getPickleStep(String text) { - return new PickleStep(text, Collections.emptyList(), Collections.emptyList()); - } - - private static StepDefinition getStepDefinitionMockWithPattern(String 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 argumentMatcher.argumentsFrom((PickleStep) invocationOnMock.getArgument(0)); - } - }); - return stepDefinition; - } -} diff --git a/core/src/test/java/cucumber/runner/StepDurationTimeService.java b/core/src/test/java/cucumber/runner/StepDurationTimeService.java deleted file mode 100644 index 8ca2e97f91..0000000000 --- a/core/src/test/java/cucumber/runner/StepDurationTimeService.java +++ /dev/null @@ -1,48 +0,0 @@ -package cucumber.runner; - -import cucumber.api.event.EventHandler; -import cucumber.api.event.EventListener; -import cucumber.api.event.EventPublisher; -import cucumber.api.event.TestStepStarted; - -import static java.util.concurrent.TimeUnit.MILLISECONDS; - -public class StepDurationTimeService implements TimeService, EventListener { - - private final ThreadLocal currentTime = new ThreadLocal<>(); - private final long stepDurationMillis; - - private EventHandler stepStartedHandler = new EventHandler() { - @Override - public void receive(TestStepStarted event) { - handleTestStepStarted(); - } - }; - - public StepDurationTimeService(long stepDurationMillis) { - this.stepDurationMillis = stepDurationMillis; - } - - @Override - public void setEventPublisher(EventPublisher publisher) { - publisher.registerHandlerFor(TestStepStarted.class, stepStartedHandler); - } - - @Override - public long time() { - Long result = currentTime.get(); - return result != null ? MILLISECONDS.toNanos(result) : 0L; - } - - @Override - public long timeMillis() { - Long result = currentTime.get(); - return result != null ? result : 0L; - } - - private void handleTestStepStarted() { - long time = timeMillis(); - currentTime.set(time + stepDurationMillis); - } - -} diff --git a/core/src/test/java/cucumber/runner/TestRunnerSupplier.java b/core/src/test/java/cucumber/runner/TestRunnerSupplier.java deleted file mode 100644 index ff43bcfe22..0000000000 --- a/core/src/test/java/cucumber/runner/TestRunnerSupplier.java +++ /dev/null @@ -1,49 +0,0 @@ -package cucumber.runner; - -import cucumber.runtime.Backend; -import io.cucumber.core.options.RuntimeOptions; -import cucumber.runtime.Glue; -import cucumber.runtime.snippets.FunctionNameGenerator; -import gherkin.pickles.PickleStep; - -import java.net.URI; -import java.util.Collections; -import java.util.List; - -import static java.util.Collections.emptyList; - -public class TestRunnerSupplier implements Backend, RunnerSupplier { - - private final EventBus bus; - private final RuntimeOptions runtimeOptions; - - protected TestRunnerSupplier(EventBus bus, RuntimeOptions runtimeOptions) { - this.bus = bus; - this.runtimeOptions = runtimeOptions; - } - - @Override - public void loadGlue(Glue glue, List gluePaths) { - - } - - @Override - public void buildWorld() { - - } - - @Override - public void disposeWorld() { - - } - - @Override - public List getSnippet(PickleStep step, String keyword, FunctionNameGenerator functionNameGenerator) { - return emptyList(); - } - - @Override - public Runner get() { - return new Runner(bus, Collections.singleton(this), runtimeOptions); - } -} diff --git a/core/src/test/java/cucumber/runner/TimeServiceStub.java b/core/src/test/java/cucumber/runner/TimeServiceStub.java deleted file mode 100644 index 49633ce893..0000000000 --- a/core/src/test/java/cucumber/runner/TimeServiceStub.java +++ /dev/null @@ -1,29 +0,0 @@ -package cucumber.runner; - -import static java.util.concurrent.TimeUnit.MILLISECONDS; - -public class TimeServiceStub implements TimeService { - private final long durationMillis; - private final ThreadLocal currentTime = new ThreadLocal<>(); - private final ThreadLocal currentTimeMillis = new ThreadLocal<>(); - - public TimeServiceStub(long durationMillis) { - this.durationMillis = durationMillis; - } - - @Override - public long time() { - Long result = currentTime.get(); - result = result != null ? result : 0L; - currentTime.set(result + durationMillis); - return MILLISECONDS.toNanos(result); - } - - @Override - public long timeMillis() { - Long result = currentTimeMillis.get(); - result = result != null ? result : 0L; - currentTimeMillis.set(result + durationMillis); - return result; - } -} diff --git a/core/src/test/java/cucumber/runner/TimeServiceTest.java b/core/src/test/java/cucumber/runner/TimeServiceTest.java deleted file mode 100644 index 0cf2f95fc6..0000000000 --- a/core/src/test/java/cucumber/runner/TimeServiceTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package cucumber.runner; - -import static org.junit.Assert.assertNull; - -import cucumber.runner.TimeService; -import org.junit.Test; - -public class TimeServiceTest { - private final TimeService stopWatch = TimeService.SYSTEM; - private Throwable exception; - - @Test - public void should_be_thread_safe() { - try { - Thread timerThreadOne = new TimerThread(500L); - Thread timerThreadTwo = new TimerThread(750L); - - timerThreadOne.start(); - timerThreadTwo.start(); - - timerThreadOne.join(); - timerThreadTwo.join(); - - assertNull("null_pointer_exception", exception); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - class TimerThread extends Thread { - private final long timeoutMillis; - - public TimerThread(long timeoutMillis) { - this.timeoutMillis = timeoutMillis; - } - - @Override - public void run() { - try { - stopWatch.time(); - Thread.sleep(timeoutMillis); - stopWatch.time(); - } catch (NullPointerException e) { - exception = e; - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - } -} diff --git a/core/src/test/java/cucumber/runner/UndefinedStepDefinitionMatchTest.java b/core/src/test/java/cucumber/runner/UndefinedStepDefinitionMatchTest.java deleted file mode 100644 index 3481aaf72e..0000000000 --- a/core/src/test/java/cucumber/runner/UndefinedStepDefinitionMatchTest.java +++ /dev/null @@ -1,24 +0,0 @@ -package cucumber.runner; - -import cucumber.api.Scenario; -import gherkin.pickles.PickleStep; -import org.junit.Test; - -import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; - -public class UndefinedStepDefinitionMatchTest { - public final UndefinedPickleStepDefinitionMatch match = new UndefinedPickleStepDefinitionMatch(mock(PickleStep.class)); - - @Test(expected=UndefinedStepDefinitionException.class) - public void throws_ambiguous_step_definitions_exception_when_run() throws Throwable { - match.runStep(mock(Scenario.class)); - fail("UndefinedStepDefinitionsException expected"); - } - - @Test(expected=UndefinedStepDefinitionException.class) - public void throws_ambiguous_step_definitions_exception_when_dry_run() throws Throwable { - match.dryRunStep(mock(Scenario.class)); - fail("UndefinedStepDefinitionsException expected"); - } -} diff --git a/core/src/test/java/cucumber/runtime/StubBackend.java b/core/src/test/java/cucumber/runtime/StubBackend.java deleted file mode 100644 index e2d503ec3e..0000000000 --- a/core/src/test/java/cucumber/runtime/StubBackend.java +++ /dev/null @@ -1,39 +0,0 @@ -package cucumber.runtime; - -import cucumber.runtime.io.ResourceLoader; -import cucumber.runtime.snippets.FunctionNameGenerator; -import gherkin.pickles.PickleStep; -import io.cucumber.stepexpression.TypeRegistry; - -import java.net.URI; -import java.util.List; - -import static java.util.Collections.emptyList; - -public class StubBackend implements Backend { - - @SuppressWarnings("unused") // reflection to create backend - public StubBackend(ResourceLoader resourceLoader, TypeRegistry typeRegistry) { - - } - - @Override - public void loadGlue(Glue glue, List gluePaths) { - - } - - @Override - public void buildWorld() { - - } - - @Override - public void disposeWorld() { - - } - - @Override - public List getSnippet(PickleStep step, String keyword, FunctionNameGenerator functionNameGenerator) { - return emptyList(); - } -} diff --git a/core/src/test/java/cucumber/runtime/StubStepDefinition.java b/core/src/test/java/cucumber/runtime/StubStepDefinition.java deleted file mode 100644 index e2cb7e5767..0000000000 --- a/core/src/test/java/cucumber/runtime/StubStepDefinition.java +++ /dev/null @@ -1,69 +0,0 @@ -package cucumber.runtime; - -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.Type; -import java.util.Arrays; -import java.util.List; - -import static org.junit.Assert.assertEquals; - -public class StubStepDefinition implements StepDefinition { - private final StepExpression expression; - private List parameters; - - public StubStepDefinition(String pattern, TypeRegistry typeRegistry, Type... types) { - this.parameters = Arrays.asList(types); - if (parameters.isEmpty()) { - this.expression = new StepExpressionFactory(typeRegistry).createExpression(pattern); - } else { - Type lastParameter = parameters.get(parameters.size() - 1); - this.expression = new StepExpressionFactory(typeRegistry).createExpression(pattern, lastParameter); - } - } - - @Override - public List matchedArguments(PickleStep step) { - ArgumentMatcher argumentMatcher = new ExpressionArgumentMatcher(expression); - return argumentMatcher.argumentsFrom(step); - } - - @Override - public String getLocation(boolean detail) { - return "{stubbed location" + (detail ? " with details" : "") + "}"; - } - - @Override - public Integer getParameterCount() { - return parameters.size(); - } - - @Override - public void execute(Object[] args) throws Throwable { - assertEquals(parameters.size(), args.length); - for (int i = 0; i < args.length; i++) { - assertEquals(parameters.get(i), args[i].getClass()); - } - } - - @Override - public boolean isDefinedAt(StackTraceElement stackTraceElement) { - return false; - } - - @Override - public String getPattern() { - return expression.getSource(); - } - - @Override - public boolean isScenarioScoped() { - return false; - } -} diff --git a/core/src/test/java/cucumber/runtime/UtilsTest.java b/core/src/test/java/cucumber/runtime/UtilsTest.java deleted file mode 100644 index 2e298f5d11..0000000000 --- a/core/src/test/java/cucumber/runtime/UtilsTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package cucumber.runtime; - -import org.junit.Test; - -import java.net.MalformedURLException; -import java.net.URL; - -import static cucumber.runtime.Utils.isInstantiable; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -public class UtilsTest { - @Test - public void public_non_static_inner_classes_are_not_instantiable() { - assertFalse(isInstantiable(NonStaticInnerClass.class)); - } - - @Test - public void public_static_inner_classes_are_instantiable() { - assertTrue(isInstantiable(StaticInnerClass.class)); - } - - public class NonStaticInnerClass { - } - - public static class StaticInnerClass { - } - - @Test - public void test_url() throws MalformedURLException { - URL dotCucumber = Utils.toURL("foo/bar/.cucumber"); - URL url = new URL(dotCucumber, "stepdefs.json"); - assertEquals(new URL("file:foo/bar/.cucumber/stepdefs.json"), url); - } -} diff --git a/core/src/test/java/cucumber/runtime/filter/FiltersTest.java b/core/src/test/java/cucumber/runtime/filter/FiltersTest.java deleted file mode 100644 index 4df2326ee2..0000000000 --- a/core/src/test/java/cucumber/runtime/filter/FiltersTest.java +++ /dev/null @@ -1,60 +0,0 @@ -package cucumber.runtime.filter; - - import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - - import java.util.ArrayList; -import java.util.List; - - import org.junit.Rule; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; - - import gherkin.events.PickleEvent; -import io.cucumber.core.options.FilterOptions; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; - - - public class FiltersTest { - - @Rule - public MockitoRule mockitoRule = MockitoJUnit.rule(); - @Mock - private FilterOptions fomock; - - Filters filters; - - private List pickles = createFilteredPickleList(8); - - @Test - public void count_less_than_pickles_list_size() { - when(fomock.getLimitCount()).thenReturn(5); - filters = new Filters(fomock); - assertThat(filters.limitPickleEvents(pickles).size(), is(5)); - } - - @Test - public void count_greater_than_pickles_list_size() { - when(fomock.getLimitCount()).thenReturn(10); - filters = new Filters(fomock); - assertThat(filters.limitPickleEvents(pickles).size(), is(8)); - } - - @Test - public void default_count_zero() { - when(fomock.getLimitCount()).thenReturn(0); - filters = new Filters(fomock); - assertThat(filters.limitPickleEvents(pickles).size(), is(8)); - } - - private List createFilteredPickleList(int size) { - List pickles = new ArrayList<>(); - for (int i = 0; i < size; i++) { - pickles.add(mock(PickleEvent.class)); - } - return pickles; - } -} \ No newline at end of file diff --git a/core/src/test/java/cucumber/runtime/formatter/AnyStepDefinitionReporter.java b/core/src/test/java/cucumber/runtime/formatter/AnyStepDefinitionReporter.java deleted file mode 100644 index eb07fa6478..0000000000 --- a/core/src/test/java/cucumber/runtime/formatter/AnyStepDefinitionReporter.java +++ /dev/null @@ -1,17 +0,0 @@ -package cucumber.runtime.formatter; - -import cucumber.api.StepDefinitionReporter; -import cucumber.runtime.StepDefinition; - -public class AnyStepDefinitionReporter implements StepDefinitionReporter { - - public AnyStepDefinitionReporter() { - // TODO Auto-generated constructor stub - } - - @Override - public void stepDefinition(StepDefinition stepDefinition) { - // TODO Auto-generated method stub - } - -} diff --git a/core/src/test/java/cucumber/runtime/formatter/PluginsTest.java b/core/src/test/java/cucumber/runtime/formatter/PluginsTest.java deleted file mode 100644 index 831190d673..0000000000 --- a/core/src/test/java/cucumber/runtime/formatter/PluginsTest.java +++ /dev/null @@ -1,109 +0,0 @@ -package cucumber.runtime.formatter; - -import cucumber.api.event.ConcurrentEventListener; -import cucumber.api.event.Event; -import cucumber.api.event.EventHandler; -import cucumber.api.event.EventListener; -import cucumber.api.event.EventPublisher; -import cucumber.api.formatter.ColorAware; -import cucumber.api.formatter.StrictAware; -import cucumber.runner.CanonicalOrderEventPublisher; -import io.cucumber.core.options.RuntimeOptions; -import io.cucumber.core.options.RuntimeOptionsBuilder; -import org.junit.Rule; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.ArgumentMatchers; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; - -import static java.lang.ClassLoader.getSystemClassLoader; -import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.quality.Strictness.STRICT_STUBS; - -public class PluginsTest { - - @Rule - public MockitoRule mockitoRule = MockitoJUnit.rule().strictness(STRICT_STUBS); - @Mock - private EventPublisher rootEventPublisher; - - private PluginFactory pluginFactory = new PluginFactory(); - - @Captor - private ArgumentCaptor eventPublisher; - - @Test - public void shouldSetStrictOnPlugin() { - RuntimeOptions runtimeOptions = new RuntimeOptionsBuilder().setStrict().build(); - Plugins plugins = new Plugins(getSystemClassLoader(), pluginFactory, runtimeOptions); - StrictAware plugin = Mockito.mock(StrictAware.class); - plugins.addPlugin(plugin); - verify(plugin).setStrict(true); - } - - - @Test - public void shouldSetMonochromeOnPlugin() { - RuntimeOptions runtimeOptions = new RuntimeOptionsBuilder().setMonochrome().build(); - Plugins plugins = new Plugins(getSystemClassLoader(), pluginFactory, runtimeOptions); - ColorAware plugin = Mockito.mock(ColorAware.class); - plugins.addPlugin(plugin); - verify(plugin).setMonochrome(true); - } - - - @Test - public void shouldSetConcurrentEventListener() { - RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); - Plugins plugins = new Plugins(getSystemClassLoader(), pluginFactory, runtimeOptions); - ConcurrentEventListener plugin = Mockito.mock(ConcurrentEventListener.class); - plugins.addPlugin(plugin); - plugins.setSerialEventBusOnEventListenerPlugins(rootEventPublisher); - verify(plugin, times(1)).setEventPublisher(rootEventPublisher); - } - - - @Test - public void shouldSetConcurrentEventListenerForSingleThread() { - RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); - - Plugins plugins = new Plugins(getSystemClassLoader(), pluginFactory, runtimeOptions); - EventListener plugin = Mockito.mock(EventListener.class); - plugins.addPlugin(plugin); - plugins.setEventBusOnEventListenerPlugins(rootEventPublisher); - verify(plugin, times(1)).setEventPublisher(rootEventPublisher); - } - - - @Test - public void shouldSetNonConcurrentEventListenerForMultiThread() { - RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); - - Plugins plugins = new Plugins(getSystemClassLoader(), pluginFactory, runtimeOptions); - EventListener plugin = Mockito.mock(EventListener.class); - plugins.addPlugin(plugin); - plugins.setSerialEventBusOnEventListenerPlugins(rootEventPublisher); - verify(plugin, times(1)).setEventPublisher(eventPublisher.capture()); - assertEquals(CanonicalOrderEventPublisher.class, eventPublisher.getValue().getClass()); - } - - - @Test - public void shouldRegisterCanonicalOrderEventPublisherWithRootEventPublisher() { - RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); - - Plugins plugins = new Plugins(getSystemClassLoader(), pluginFactory, runtimeOptions); - EventListener plugin = Mockito.mock(EventListener.class); - plugins.addPlugin(plugin); - plugins.setSerialEventBusOnEventListenerPlugins(rootEventPublisher); - verify(rootEventPublisher, times(1)).registerHandlerFor(eq(Event.class), ArgumentMatchers.>any()); - } - -} diff --git a/core/src/test/java/cucumber/runtime/model/CucumberFeatureTest.java b/core/src/test/java/cucumber/runtime/model/CucumberFeatureTest.java deleted file mode 100644 index 8ba8272a2e..0000000000 --- a/core/src/test/java/cucumber/runtime/model/CucumberFeatureTest.java +++ /dev/null @@ -1,75 +0,0 @@ -package cucumber.runtime.model; - -import cucumber.runtime.io.Resource; -import cucumber.runtime.io.ResourceLoader; -import org.junit.Test; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.net.URI; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import static java.util.Collections.singletonList; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class CucumberFeatureTest { - - private static final String DOES_NOT_EXIST = "does/not/exist"; - - @Test - public void succeeds_if_no_features_are_found() { - ResourceLoader resourceLoader = mock(ResourceLoader.class); - mockNonExistingResource(resourceLoader); - new FeatureLoader(resourceLoader).load(feature("does/not/exist")); - } - - private void mockNonExistingResource(ResourceLoader resourceLoader) { - when(resourceLoader.resources(URI.create(DOES_NOT_EXIST), ".feature")).thenReturn(Collections.emptyList()); - } - - - @Test - public void gives_error_message_if_path_does_not_exist() { - ResourceLoader resourceLoader = mock(ResourceLoader.class); - mockFeaturePathToNotExist(resourceLoader, "path/bar.feature"); - try { - new FeatureLoader(resourceLoader).load(feature("path/bar.feature")); - fail("IllegalArgumentException was expected"); - } catch (IllegalArgumentException exception) { - assertEquals("Not a file or directory: path/bar.feature", exception.getMessage()); - } - } - - public List feature(String s) { - return singletonList(URI.create(s)); - } - - @Test - public void gives_error_message_if_feature_on_class_path_does_not_exist() { - ResourceLoader resourceLoader = mock(ResourceLoader.class); - mockFeaturePathToNotExist(resourceLoader, "classpath:path/bar.feature"); - try { - new FeatureLoader(resourceLoader).load(feature("classpath:path/bar.feature")); - fail("IllegalArgumentException was expected"); - } catch (IllegalArgumentException exception) { - assertEquals("Feature not found: classpath:path/bar.feature", exception.getMessage()); - } - } - - private void mockFeaturePathToNotExist(ResourceLoader resourceLoader, String featurePath) { - if (featurePath.startsWith("classpath")) { - when(resourceLoader.resources(URI.create(featurePath), ".feature")).thenReturn(new ArrayList()); - } else { - when(resourceLoader.resources(URI.create(featurePath), ".feature")).thenThrow(new IllegalArgumentException("Not a file or directory: " + featurePath)); - } - } - - private String suffix(String suffix) { - return suffix; - } -} diff --git a/java/src/test/java/cucumber/runtime/java/JavaObjectFactoryTest.java b/core/src/test/java/io/cucumber/core/backend/JavaObjectFactoryTest.java similarity index 85% rename from java/src/test/java/cucumber/runtime/java/JavaObjectFactoryTest.java rename to core/src/test/java/io/cucumber/core/backend/JavaObjectFactoryTest.java index 3f9dbb83b1..67be0c3a40 100644 --- a/java/src/test/java/cucumber/runtime/java/JavaObjectFactoryTest.java +++ b/core/src/test/java/io/cucumber/core/backend/JavaObjectFactoryTest.java @@ -1,6 +1,6 @@ -package cucumber.runtime.java; +package io.cucumber.core.backend; -import cucumber.api.java.ObjectFactory; +import io.cucumber.core.backend.ObjectFactoryServiceLoader.DefaultJavaObjectFactory; import org.junit.Test; import static org.junit.Assert.assertNotNull; diff --git a/core/src/test/java/io/cucumber/core/backend/StubBackendProviderService.java b/core/src/test/java/io/cucumber/core/backend/StubBackendProviderService.java new file mode 100644 index 0000000000..2ac1f83c25 --- /dev/null +++ b/core/src/test/java/io/cucumber/core/backend/StubBackendProviderService.java @@ -0,0 +1,40 @@ +package io.cucumber.core.backend; + +import io.cucumber.core.io.ResourceLoader; +import io.cucumber.core.snippets.Snippet; +import io.cucumber.core.snippets.TestSnippet; + +import java.net.URI; +import java.util.List; + +public class StubBackendProviderService implements BackendProviderService { + @Override + public Backend create(Lookup lookup, Container container, ResourceLoader resourceLoader) { + return new StubBackend(); + } + + static class StubBackend implements Backend { + + @Override + public void loadGlue(Glue glue, List gluePaths) { + + } + + @Override + public void buildWorld() { + + } + + @Override + public void disposeWorld() { + + } + + @Override + public Snippet getSnippet(){ + return new TestSnippet(); + } + + } + +} diff --git a/core/src/test/java/io/cucumber/core/feature/CucumberFeatureTest.java b/core/src/test/java/io/cucumber/core/feature/CucumberFeatureTest.java new file mode 100644 index 0000000000..09c8ff0858 --- /dev/null +++ b/core/src/test/java/io/cucumber/core/feature/CucumberFeatureTest.java @@ -0,0 +1,46 @@ +package io.cucumber.core.feature; + +import io.cucumber.core.io.ResourceLoader; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.net.URI; +import java.util.Collections; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class CucumberFeatureTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void succeeds_if_no_features_are_found() { + URI featurePath = URI.create("does/not/exist"); + ResourceLoader resourceLoader = mock(ResourceLoader.class); + when(resourceLoader.resources(featurePath, ".feature")).thenReturn(Collections.emptyList()); + new FeatureLoader(resourceLoader).load(singletonList(featurePath)); + } + + @Test + public void gives_error_message_if_path_does_not_exist() { + URI featurePath = URI.create("path/bar.feature"); + ResourceLoader resourceLoader = mock(ResourceLoader.class); + when(resourceLoader.resources(featurePath, ".feature")).thenThrow(new IllegalArgumentException("Not a file or directory: " + "path/bar.feature")); + expectedException.expectMessage("Not a file or directory: path/bar.feature"); + new FeatureLoader(resourceLoader).load(singletonList(featurePath)); + } + + @Test + public void gives_error_message_if_feature_on_class_path_does_not_exist() { + URI featurePath = URI.create("classpath:path/bar.feature"); + ResourceLoader resourceLoader = mock(ResourceLoader.class); + when(resourceLoader.resources(featurePath, ".feature")).thenReturn(emptyList()); + expectedException.expectMessage("Feature not found: classpath:path/bar.feature"); + new FeatureLoader(resourceLoader).load(singletonList(featurePath)); + } +} diff --git a/core/src/test/java/cucumber/util/EncodingTest.java b/core/src/test/java/io/cucumber/core/feature/EncodingTest.java similarity index 80% rename from core/src/test/java/cucumber/util/EncodingTest.java rename to core/src/test/java/io/cucumber/core/feature/EncodingTest.java index 2975b73d29..88b710130c 100644 --- a/core/src/test/java/cucumber/util/EncodingTest.java +++ b/core/src/test/java/io/cucumber/core/feature/EncodingTest.java @@ -1,36 +1,35 @@ -package cucumber.util; - -import static org.junit.Assert.assertFalse; -import static org.mockito.Mockito.when; - -import java.io.FileInputStream; -import java.io.IOException; - -import org.junit.Rule; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; - -import cucumber.runtime.io.Resource; - -public class EncodingTest { - - @Rule - public MockitoRule mockitoRule = MockitoJUnit.rule(); - - @Mock - private Resource resource; - - @Test - public void test_utf8_bom_encode() throws RuntimeException, IOException { - when(resource.getInputStream()).thenReturn(new FileInputStream("src/test/resources/cucumber/util/UTF_8_BOM_Encoded.feature")); - assertFalse("UTF-8 BOM encoding not removed.", Encoding.readFile(resource).startsWith("\uFEFF")); - } - - @Test - public void test_utf8_encode() throws RuntimeException, IOException { - when(resource.getInputStream()).thenReturn(new FileInputStream("src/test/resources/cucumber/util/UTF_8_Encoded.feature")); - assertFalse("UTF-8 BOM encoding should not be present.", Encoding.readFile(resource).startsWith("\uFEFF")); - } +package io.cucumber.core.feature; + +import static org.junit.Assert.assertFalse; +import static org.mockito.Mockito.when; + +import java.io.FileInputStream; +import java.io.IOException; + +import io.cucumber.core.io.Resource; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +public class EncodingTest { + + @Rule + public MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock + private Resource resource; + + @Test + public void test_utf8_bom_encode() throws RuntimeException, IOException { + when(resource.getInputStream()).thenReturn(new FileInputStream("src/test/resources/io/cucumber/core/feature/UTF_8_BOM_Encoded.feature")); + assertFalse("UTF-8 BOM encoding not removed.", Encoding.readFile(resource).startsWith("\uFEFF")); + } + + @Test + public void test_utf8_encode() throws RuntimeException, IOException { + when(resource.getInputStream()).thenReturn(new FileInputStream("src/test/resources/io/cucumber/core/feature/UTF_8_Encoded.feature")); + assertFalse("UTF-8 BOM encoding should not be present.", Encoding.readFile(resource).startsWith("\uFEFF")); + } } \ No newline at end of file diff --git a/core/src/test/java/cucumber/runtime/model/FeatureBuilderTest.java b/core/src/test/java/io/cucumber/core/feature/FeatureBuilderTest.java similarity index 95% rename from core/src/test/java/cucumber/runtime/model/FeatureBuilderTest.java rename to core/src/test/java/io/cucumber/core/feature/FeatureBuilderTest.java index 4c4d326ec3..3b72bfb42d 100644 --- a/core/src/test/java/cucumber/runtime/model/FeatureBuilderTest.java +++ b/core/src/test/java/io/cucumber/core/feature/FeatureBuilderTest.java @@ -1,6 +1,6 @@ -package cucumber.runtime.model; +package io.cucumber.core.feature; -import cucumber.runtime.io.Resource; +import io.cucumber.core.io.Resource; import org.junit.Test; import java.io.ByteArrayInputStream; diff --git a/core/src/test/java/io/cucumber/core/model/FeatureIdentifierTest.java b/core/src/test/java/io/cucumber/core/feature/FeatureIdentifierTest.java similarity index 96% rename from core/src/test/java/io/cucumber/core/model/FeatureIdentifierTest.java rename to core/src/test/java/io/cucumber/core/feature/FeatureIdentifierTest.java index a603afb9b6..86953d7a60 100644 --- a/core/src/test/java/io/cucumber/core/model/FeatureIdentifierTest.java +++ b/core/src/test/java/io/cucumber/core/feature/FeatureIdentifierTest.java @@ -1,4 +1,4 @@ -package io.cucumber.core.model; +package io.cucumber.core.feature; import org.junit.Rule; import org.junit.Test; diff --git a/core/src/test/java/io/cucumber/core/model/FeaturePathTest.java b/core/src/test/java/io/cucumber/core/feature/FeaturePathTest.java similarity index 99% rename from core/src/test/java/io/cucumber/core/model/FeaturePathTest.java rename to core/src/test/java/io/cucumber/core/feature/FeaturePathTest.java index b928fe0d8a..57e0762a8e 100644 --- a/core/src/test/java/io/cucumber/core/model/FeaturePathTest.java +++ b/core/src/test/java/io/cucumber/core/feature/FeaturePathTest.java @@ -1,4 +1,4 @@ -package io.cucumber.core.model; +package io.cucumber.core.feature; import org.hamcrest.CustomTypeSafeMatcher; import org.hamcrest.Matcher; diff --git a/core/src/test/java/io/cucumber/core/model/FeatureWithLinesTest.java b/core/src/test/java/io/cucumber/core/feature/FeatureWithLinesTest.java similarity index 97% rename from core/src/test/java/io/cucumber/core/model/FeatureWithLinesTest.java rename to core/src/test/java/io/cucumber/core/feature/FeatureWithLinesTest.java index 2de3649efe..30ffd220cf 100644 --- a/core/src/test/java/io/cucumber/core/model/FeatureWithLinesTest.java +++ b/core/src/test/java/io/cucumber/core/feature/FeatureWithLinesTest.java @@ -1,4 +1,4 @@ -package io.cucumber.core.model; +package io.cucumber.core.feature; import org.junit.Test; diff --git a/core/src/test/java/io/cucumber/core/model/GluePathTest.java b/core/src/test/java/io/cucumber/core/feature/GluePathTest.java similarity index 98% rename from core/src/test/java/io/cucumber/core/model/GluePathTest.java rename to core/src/test/java/io/cucumber/core/feature/GluePathTest.java index 1f43b9472e..e0abda8b98 100644 --- a/core/src/test/java/io/cucumber/core/model/GluePathTest.java +++ b/core/src/test/java/io/cucumber/core/feature/GluePathTest.java @@ -1,4 +1,4 @@ -package io.cucumber.core.model; +package io.cucumber.core.feature; import org.junit.Rule; import org.junit.Test; diff --git a/core/src/test/java/io/cucumber/core/filter/FiltersTest.java b/core/src/test/java/io/cucumber/core/filter/FiltersTest.java new file mode 100644 index 0000000000..38da6098a4 --- /dev/null +++ b/core/src/test/java/io/cucumber/core/filter/FiltersTest.java @@ -0,0 +1,58 @@ +package io.cucumber.core.filter; + +import gherkin.events.PickleEvent; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + + +public class FiltersTest { + + @Rule + public MockitoRule mockitoRule = MockitoJUnit.rule(); + @Mock + private Options fomock; + + private Filters filters; + + private List pickles = createFilteredPickleList(8); + + @Test + public void count_less_than_pickles_list_size() { + when(fomock.getLimitCount()).thenReturn(5); + filters = new Filters(fomock); + assertThat(filters.limitPickleEvents(pickles).size(), is(5)); + } + + @Test + public void count_greater_than_pickles_list_size() { + when(fomock.getLimitCount()).thenReturn(10); + filters = new Filters(fomock); + assertThat(filters.limitPickleEvents(pickles).size(), is(8)); + } + + @Test + public void default_count_zero() { + when(fomock.getLimitCount()).thenReturn(0); + filters = new Filters(fomock); + assertThat(filters.limitPickleEvents(pickles).size(), is(8)); + } + + private List createFilteredPickleList(int size) { + List pickles = new ArrayList<>(); + for (int i = 0; i < size; i++) { + pickles.add(mock(PickleEvent.class)); + } + return pickles; + } +} \ No newline at end of file diff --git a/core/src/test/java/cucumber/runtime/filter/LinePredicateTest.java b/core/src/test/java/io/cucumber/core/filter/LinePredicateTest.java similarity index 98% rename from core/src/test/java/cucumber/runtime/filter/LinePredicateTest.java rename to core/src/test/java/io/cucumber/core/filter/LinePredicateTest.java index a57bb1117f..56154e5efb 100644 --- a/core/src/test/java/cucumber/runtime/filter/LinePredicateTest.java +++ b/core/src/test/java/io/cucumber/core/filter/LinePredicateTest.java @@ -1,4 +1,4 @@ -package cucumber.runtime.filter; +package io.cucumber.core.filter; import gherkin.events.PickleEvent; import gherkin.pickles.Pickle; diff --git a/core/src/test/java/cucumber/runtime/filter/NamePredicateTest.java b/core/src/test/java/io/cucumber/core/filter/NamePredicateTest.java similarity index 98% rename from core/src/test/java/cucumber/runtime/filter/NamePredicateTest.java rename to core/src/test/java/io/cucumber/core/filter/NamePredicateTest.java index c04cda7a6f..365fc206ea 100644 --- a/core/src/test/java/cucumber/runtime/filter/NamePredicateTest.java +++ b/core/src/test/java/io/cucumber/core/filter/NamePredicateTest.java @@ -1,4 +1,4 @@ -package cucumber.runtime.filter; +package io.cucumber.core.filter; import gherkin.events.PickleEvent; import gherkin.pickles.Pickle; diff --git a/core/src/test/java/cucumber/runtime/filter/TagPredicateTest.java b/core/src/test/java/io/cucumber/core/filter/TagPredicateTest.java similarity index 79% rename from core/src/test/java/cucumber/runtime/filter/TagPredicateTest.java rename to core/src/test/java/io/cucumber/core/filter/TagPredicateTest.java index ea7b0ccaec..928cbf4a7b 100644 --- a/core/src/test/java/cucumber/runtime/filter/TagPredicateTest.java +++ b/core/src/test/java/io/cucumber/core/filter/TagPredicateTest.java @@ -1,4 +1,4 @@ -package cucumber.runtime.filter; +package io.cucumber.core.filter; import gherkin.events.PickleEvent; import gherkin.pickles.Pickle; @@ -27,13 +27,20 @@ public class TagPredicateTest { private static final String NOT_FOO_TAG_VALUE = "not @FOO"; private static final String FOO_OR_BAR_TAG_VALUE = "@FOO or @BAR"; private static final String FOO_AND_BAR_TAG_VALUE = "@FOO and @BAR"; - private static final String OLD_STYLE_NOT_FOO_TAG_VALUE = "~@FOO"; - private static final String OLD_STYLE_FOO_OR_BAR_TAG_VALUE = "@FOO,@BAR"; @Test public void empty_tag_predicate_matches_pickle_with_any_tags() { PickleEvent pickleEvent = createPickleWithTags(asList(FOO_TAG)); - TagPredicate predicate = new TagPredicate(null); + TagPredicate predicate = new TagPredicate(""); + + assertTrue(predicate.apply(pickleEvent)); + } + + + @Test + public void list_of_empty_tag_predicates_matches_pickle_with_any_tags() { + PickleEvent pickleEvent = createPickleWithTags(asList(FOO_TAG)); + TagPredicate predicate = new TagPredicate(asList("", "")); assertTrue(predicate.apply(pickleEvent)); } @@ -126,38 +133,6 @@ public void or_tag_predicate_does_not_match_pickle_none_of_the_tags() { assertFalse(predicate.apply(pickleEvent)); } - @Test - public void old_style_not_tag_predicate_is_handled() { - PickleEvent pickleEvent = createPickleWithTags(asList(BAR_TAG)); - TagPredicate predicate = new TagPredicate(asList(OLD_STYLE_NOT_FOO_TAG_VALUE)); - - assertTrue(predicate.apply(pickleEvent)); - } - - @Test - public void old_style_or_tag_predicate_is_handled() { - PickleEvent pickleEvent = createPickleWithTags(asList(FOO_TAG)); - TagPredicate predicate = new TagPredicate(asList(OLD_STYLE_FOO_OR_BAR_TAG_VALUE)); - - assertTrue(predicate.apply(pickleEvent)); - } - - @Test - public void multiple_tag_expressions_are_combined_with_and() { - PickleEvent pickleEvent = createPickleWithTags(asList(FOO_TAG, BAR_TAG)); - TagPredicate predicate = new TagPredicate(asList(FOO_TAG_VALUE, BAR_TAG_VALUE)); - - assertTrue(predicate.apply(pickleEvent)); - } - - @Test - public void old_and_new_style_tag_expressions_can_be_combined() { - PickleEvent pickleEvent = createPickleWithTags(asList(BAR_TAG)); - TagPredicate predicate = new TagPredicate(asList(BAR_TAG_VALUE, OLD_STYLE_NOT_FOO_TAG_VALUE)); - - assertTrue(predicate.apply(pickleEvent)); - } - private PickleEvent createPickleWithTags(List tags) { return new PickleEvent("uri", new Pickle(NAME, LANGUAGE, NO_STEPS, tags, asList(MOCK_LOCATION))); } diff --git a/core/src/test/java/cucumber/runtime/io/DelegatingResourceIteratorFactoryTest.java b/core/src/test/java/io/cucumber/core/io/DelegatingResourceIteratorFactoryTest.java similarity index 96% rename from core/src/test/java/cucumber/runtime/io/DelegatingResourceIteratorFactoryTest.java rename to core/src/test/java/io/cucumber/core/io/DelegatingResourceIteratorFactoryTest.java index ad089e39d1..e7504f5890 100644 --- a/core/src/test/java/cucumber/runtime/io/DelegatingResourceIteratorFactoryTest.java +++ b/core/src/test/java/io/cucumber/core/io/DelegatingResourceIteratorFactoryTest.java @@ -1,4 +1,4 @@ -package cucumber.runtime.io; +package io.cucumber.core.io; import org.junit.Test; diff --git a/core/src/test/java/cucumber/runtime/io/FileResourceTest.java b/core/src/test/java/io/cucumber/core/io/FileResourceTest.java similarity index 97% rename from core/src/test/java/cucumber/runtime/io/FileResourceTest.java rename to core/src/test/java/io/cucumber/core/io/FileResourceTest.java index d8db20d070..5059d64279 100644 --- a/core/src/test/java/cucumber/runtime/io/FileResourceTest.java +++ b/core/src/test/java/io/cucumber/core/io/FileResourceTest.java @@ -1,4 +1,4 @@ -package cucumber.runtime.io; +package io.cucumber.core.io; import org.junit.Test; diff --git a/core/src/test/java/cucumber/runtime/io/FlatteningIteratorTest.java b/core/src/test/java/io/cucumber/core/io/FlatteningIteratorTest.java similarity index 97% rename from core/src/test/java/cucumber/runtime/io/FlatteningIteratorTest.java rename to core/src/test/java/io/cucumber/core/io/FlatteningIteratorTest.java index 3abf56c8bb..e5e7b2e6a3 100644 --- a/core/src/test/java/cucumber/runtime/io/FlatteningIteratorTest.java +++ b/core/src/test/java/io/cucumber/core/io/FlatteningIteratorTest.java @@ -1,4 +1,4 @@ -package cucumber.runtime.io; +package io.cucumber.core.io; import org.junit.Test; diff --git a/core/src/test/java/cucumber/runtime/io/HelpersTest.java b/core/src/test/java/io/cucumber/core/io/HelpersTest.java similarity index 93% rename from core/src/test/java/cucumber/runtime/io/HelpersTest.java rename to core/src/test/java/io/cucumber/core/io/HelpersTest.java index e643ae6709..c6ffdc370f 100644 --- a/core/src/test/java/cucumber/runtime/io/HelpersTest.java +++ b/core/src/test/java/io/cucumber/core/io/HelpersTest.java @@ -1,10 +1,10 @@ -package cucumber.runtime.io; +package io.cucumber.core.io; import org.junit.Test; import java.net.URI; -import static cucumber.runtime.io.Helpers.jarFilePath; +import static io.cucumber.core.io.Helpers.jarFilePath; import static org.junit.Assert.assertEquals; public class HelpersTest { diff --git a/core/src/test/java/cucumber/runtime/io/ResourceLoaderTest.java b/core/src/test/java/io/cucumber/core/io/ResourceLoaderTest.java similarity index 77% rename from core/src/test/java/cucumber/runtime/io/ResourceLoaderTest.java rename to core/src/test/java/io/cucumber/core/io/ResourceLoaderTest.java index 1ebc839d2d..750334569d 100644 --- a/core/src/test/java/cucumber/runtime/io/ResourceLoaderTest.java +++ b/core/src/test/java/io/cucumber/core/io/ResourceLoaderTest.java @@ -1,6 +1,6 @@ -package cucumber.runtime.io; +package io.cucumber.core.io; -import io.cucumber.core.model.FeaturePath; +import io.cucumber.core.feature.FeaturePath; import org.junit.Test; import java.net.URI; @@ -13,21 +13,21 @@ public class ResourceLoaderTest { @Test public void loads_resources_from_filesystem_dir() { - URI uri = FeaturePath.parse("src/test/resources/cucumber/runtime"); + URI uri = FeaturePath.parse("src/test/resources/io/cucumber/core"); Iterable files = new FileResourceLoader().resources(uri, ".properties"); assertEquals(3, toList(files).size()); } @Test public void loads_resource_from_filesystem_file() { - URI uri = FeaturePath.parse("src/test/resources/cucumber/runtime/bar.properties"); + URI uri = FeaturePath.parse("src/test/resources/io/cucumber/core/bar.properties"); Iterable files = new FileResourceLoader().resources(uri, ".doesntmatter"); assertEquals(1, toList(files).size()); } @Test public void loads_resources_from_jar_on_classpath() { - URI uri = FeaturePath.parse("classpath:cucumber"); + URI uri = FeaturePath.parse("classpath:io/cucumber"); Iterable files = new ClasspathResourceLoader(Thread.currentThread().getContextClassLoader()).resources(uri, ".properties"); assertEquals(4, toList(files).size()); } diff --git a/core/src/test/java/io/cucumber/core/io/TestClasspathResourceLoader.java b/core/src/test/java/io/cucumber/core/io/TestClasspathResourceLoader.java new file mode 100644 index 0000000000..9bf3e79379 --- /dev/null +++ b/core/src/test/java/io/cucumber/core/io/TestClasspathResourceLoader.java @@ -0,0 +1,10 @@ +package io.cucumber.core.io; + + +public class TestClasspathResourceLoader { + + public static ResourceLoader create(ClassLoader classLoader) { + return new ClasspathResourceLoader(classLoader); + } + +} \ No newline at end of file diff --git a/core/src/test/java/cucumber/runtime/io/TestResourceIterator.java b/core/src/test/java/io/cucumber/core/io/TestResourceIterator.java similarity index 93% rename from core/src/test/java/cucumber/runtime/io/TestResourceIterator.java rename to core/src/test/java/io/cucumber/core/io/TestResourceIterator.java index 38e2bc5eb5..78f294120e 100644 --- a/core/src/test/java/cucumber/runtime/io/TestResourceIterator.java +++ b/core/src/test/java/io/cucumber/core/io/TestResourceIterator.java @@ -1,4 +1,4 @@ -package cucumber.runtime.io; +package io.cucumber.core.io; import java.util.Iterator; import java.util.NoSuchElementException; diff --git a/core/src/test/java/cucumber/runtime/io/TestResourceIteratorFactory.java b/core/src/test/java/io/cucumber/core/io/TestResourceIteratorFactory.java similarity index 94% rename from core/src/test/java/cucumber/runtime/io/TestResourceIteratorFactory.java rename to core/src/test/java/io/cucumber/core/io/TestResourceIteratorFactory.java index 599a362c83..9b97ec30fa 100644 --- a/core/src/test/java/cucumber/runtime/io/TestResourceIteratorFactory.java +++ b/core/src/test/java/io/cucumber/core/io/TestResourceIteratorFactory.java @@ -1,4 +1,4 @@ -package cucumber.runtime.io; +package io.cucumber.core.io; import java.net.URI; import java.net.URL; diff --git a/core/src/test/java/cucumber/runtime/io/ZipResourceIteratorFactoryTest.java b/core/src/test/java/io/cucumber/core/io/ZipResourceIteratorFactoryTest.java similarity index 97% rename from core/src/test/java/cucumber/runtime/io/ZipResourceIteratorFactoryTest.java rename to core/src/test/java/io/cucumber/core/io/ZipResourceIteratorFactoryTest.java index bb923a9eee..e4363d8b91 100644 --- a/core/src/test/java/cucumber/runtime/io/ZipResourceIteratorFactoryTest.java +++ b/core/src/test/java/io/cucumber/core/io/ZipResourceIteratorFactoryTest.java @@ -1,4 +1,4 @@ -package cucumber.runtime.io; +package io.cucumber.core.io; import org.junit.Test; diff --git a/core/src/test/java/cucumber/runtime/io/ZipResourceTest.java b/core/src/test/java/io/cucumber/core/io/ZipResourceTest.java similarity index 95% rename from core/src/test/java/cucumber/runtime/io/ZipResourceTest.java rename to core/src/test/java/io/cucumber/core/io/ZipResourceTest.java index b0793bc703..a26371b0f5 100644 --- a/core/src/test/java/cucumber/runtime/io/ZipResourceTest.java +++ b/core/src/test/java/io/cucumber/core/io/ZipResourceTest.java @@ -1,4 +1,4 @@ -package cucumber.runtime.io; +package io.cucumber.core.io; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; diff --git a/core/src/main/java/cucumber/api/CucumberOptions.java b/core/src/test/java/io/cucumber/core/options/CucumberOptions.java similarity index 68% rename from core/src/main/java/cucumber/api/CucumberOptions.java rename to core/src/test/java/io/cucumber/core/options/CucumberOptions.java index 1af8517a8b..9d560b98d6 100644 --- a/core/src/main/java/cucumber/api/CucumberOptions.java +++ b/core/src/test/java/io/cucumber/core/options/CucumberOptions.java @@ -1,4 +1,6 @@ -package cucumber.api; +package io.cucumber.core.options; + +import io.cucumber.core.snippets.SnippetType; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -6,22 +8,25 @@ import java.lang.annotation.Target; /** - * Configure Cucumbers options. + * This annotation provides the same options as the cucumber command line, {@link io.cucumber.core.cli.Main}. * * @deprecated use either {@code io.cucumber.junit.CucumberOptions} or {@code io.cucumber.testng.CucumberOptions}. */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) -@Deprecated public @interface CucumberOptions { /** * Skip execution of glue code. + * + * @return True when dry run, false otherwise. */ boolean dryRun() default false; /** * Treat undefined and pending steps as errors. + * + * @return True when strict, false otherwise. */ boolean strict() default false; @@ -33,7 +38,8 @@ * class. For example, if the annotated class is {@code com.example.RunCucumber} * then features are assumed to be located in {@code classpath:com/example}. * - * @see io.cucumber.core.model.FeatureWithLines + * @see io.cucumber.core.feature.FeatureWithLines + * @return The location(s) of the features. */ String[] features() default {}; @@ -45,7 +51,8 @@ * class. For example, if the annotated class is {@code com.example.RunCucumber} * then glue is assumed to be located in {@code com.example}. * - * @see io.cucumber.core.model.GluePath + * @see io.cucumber.core.feature.GluePath + * @return The package(s) that contain glue code. */ String[] glue() default {}; @@ -54,6 +61,8 @@ * plugins) from. E.g: {@code com.example.app} *

* These packages are used in addition to the default described in {@code #glue}. + * + * @return The package(s) that contain the extra glue code. */ String[] extraGlue() default {}; @@ -61,6 +70,8 @@ * Only run scenarios tagged with tags matching {@code TAG_EXPRESSION}. *

* For example {@code "@smoke and not @fast"}. + * + * @return The tags that should be matched. */ String[] tags() default {}; @@ -76,27 +87,44 @@ * Plugins can be provided with an argument. For example * {@code json:target/cucumber-report.json} * - * @see Plugin + * @see io.cucumber.core.plugin.Plugin + * @return The plugins that should be added. */ String[] plugin() default {}; /** * Don't colour terminal output. + * + * @return True when no color should be present in the terminal output, false when color is allowed. */ boolean monochrome() default false; /** * Only run scenarios whose names match provided regular expression. + * + * @return The name(s) that should be matched via regular expressions. */ String[] name() default {}; /** * Format of the generated snippets. + * + * @see SnippetType + * @return The snippet type to be generated for missing steps. */ SnippetType snippets() default SnippetType.UNDERSCORE; + /** + * A custom ObjectFactory. + * + * @return The class of the custim ObjectFactory to use. + */ + Class objectFactory() default NoObjectFactory.class; + /** * Pass options to the JUnit runner. + * + * @return The JUnit options to pass on. */ String[] junit() default {}; diff --git a/core/src/test/java/cucumber/runtime/CucumberOptionsAnnotationParserTest.java b/core/src/test/java/io/cucumber/core/options/CucumberOptionsAnnotationParserTest.java similarity index 52% rename from core/src/test/java/cucumber/runtime/CucumberOptionsAnnotationParserTest.java rename to core/src/test/java/io/cucumber/core/options/CucumberOptionsAnnotationParserTest.java index 0be11c66bf..b5487fed4b 100644 --- a/core/src/test/java/cucumber/runtime/CucumberOptionsAnnotationParserTest.java +++ b/core/src/test/java/io/cucumber/core/options/CucumberOptionsAnnotationParserTest.java @@ -1,17 +1,16 @@ -package cucumber.runtime; - -import cucumber.api.CucumberOptions; -import cucumber.api.Plugin; -import cucumber.api.SnippetType; -import cucumber.runner.TimeService; -import cucumber.runner.TimeServiceEventBus; -import cucumber.runtime.formatter.PluginFactory; -import cucumber.runtime.formatter.Plugins; -import io.cucumber.core.options.RuntimeOptions; -import io.cucumber.core.options.CucumberOptionsAnnotationParser; +package io.cucumber.core.options; + +import io.cucumber.core.plugin.Plugin; +import io.cucumber.core.snippets.SnippetType; +import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.exception.CucumberException; +import io.cucumber.core.plugin.PluginFactory; +import io.cucumber.core.plugin.Plugins; +import io.cucumber.core.runtime.TimeServiceEventBus; import org.junit.Test; import java.net.URI; +import java.time.Clock; import java.util.Iterator; import java.util.List; import java.util.regex.Pattern; @@ -21,38 +20,45 @@ import static org.hamcrest.collection.IsIterableContainingInOrder.contains; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; public class CucumberOptionsAnnotationParserTest { @Test public void create_strict() { - RuntimeOptions runtimeOptions = new CucumberOptionsAnnotationParser().parse(Strict.class).build(); + RuntimeOptions runtimeOptions = parser().parse(Strict.class).build(); assertTrue(runtimeOptions.isStrict()); } + private CucumberOptionsAnnotationParser parser() { + return new CucumberOptionsAnnotationParser() + .withOptionsProvider(new CoreCucumberOptionsProvider()); + } + @Test public void create_non_strict() { - RuntimeOptions runtimeOptions = new CucumberOptionsAnnotationParser().parse(NotStrict.class).build(); + RuntimeOptions runtimeOptions = parser().parse(NotStrict.class).build(); assertFalse(runtimeOptions.isStrict()); } @Test public void create_without_options() { - RuntimeOptions runtimeOptions = new CucumberOptionsAnnotationParser() + RuntimeOptions runtimeOptions = parser() .parse(WithoutOptions.class) .addDefaultSummaryPrinterIfNotPresent() .addDefaultFormatterIfNotPresent() .build(); assertFalse(runtimeOptions.isStrict()); - assertThat(runtimeOptions.getFeaturePaths(), contains(uri("classpath:cucumber/runtime"))); - assertThat(runtimeOptions.getGlue(), contains(uri("classpath:cucumber/runtime"))); - Plugins plugins = new Plugins(getClass().getClassLoader(), new PluginFactory(), runtimeOptions); - plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(TimeService.SYSTEM)); + assertNull(runtimeOptions.getObjectFactoryClass()); + assertThat(runtimeOptions.getFeaturePaths(), contains(uri("classpath:io/cucumber/core/options"))); + assertThat(runtimeOptions.getGlue(), contains(uri("classpath:io/cucumber/core/options"))); + Plugins plugins = new Plugins(new PluginFactory(), runtimeOptions); + plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(Clock.systemUTC())); assertThat(plugins.getPlugins(), hasSize(2)); - assertPluginExists(plugins.getPlugins(), "cucumber.runtime.formatter.ProgressFormatter"); - assertPluginExists(plugins.getPlugins(), "cucumber.runtime.formatter.DefaultSummaryPrinter"); + assertPluginExists(plugins.getPlugins(), "io.cucumber.core.plugin.ProgressFormatter"); + assertPluginExists(plugins.getPlugins(), "io.cucumber.core.plugin.DefaultSummaryPrinter"); } public static URI uri(String str) { @@ -62,33 +68,33 @@ public static URI uri(String str) { @Test public void create_without_options_with_base_class_without_options() { Class subClassWithMonoChromeTrueClass = WithoutOptionsWithBaseClassWithoutOptions.class; - RuntimeOptions runtimeOptions = new CucumberOptionsAnnotationParser() + RuntimeOptions runtimeOptions = parser() .parse(subClassWithMonoChromeTrueClass) .addDefaultFormatterIfNotPresent() .addDefaultSummaryPrinterIfNotPresent() .build(); - Plugins plugins = new Plugins(getClass().getClassLoader(), new PluginFactory(), runtimeOptions); - plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(TimeService.SYSTEM)); + Plugins plugins = new Plugins(new PluginFactory(), runtimeOptions); + plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(Clock.systemUTC())); - assertThat(runtimeOptions.getFeaturePaths(), contains(uri("classpath:cucumber/runtime"))); - assertThat(runtimeOptions.getGlue(), contains(uri("classpath:cucumber/runtime"))); + assertThat(runtimeOptions.getFeaturePaths(), contains(uri("classpath:io/cucumber/core/options"))); + assertThat(runtimeOptions.getGlue(), contains(uri("classpath:io/cucumber/core/options"))); assertThat(plugins.getPlugins(), hasSize(2)); - assertPluginExists(plugins.getPlugins(), "cucumber.runtime.formatter.ProgressFormatter"); - assertPluginExists(plugins.getPlugins(), "cucumber.runtime.formatter.DefaultSummaryPrinter"); + assertPluginExists(plugins.getPlugins(), "io.cucumber.core.plugin.ProgressFormatter"); + assertPluginExists(plugins.getPlugins(), "io.cucumber.core.plugin.DefaultSummaryPrinter"); } @Test public void create_with_no_name() { - RuntimeOptions runtimeOptions = new CucumberOptionsAnnotationParser().parse(NoName.class).build(); - assertTrue(runtimeOptions.getTagFilters().isEmpty()); + RuntimeOptions runtimeOptions = parser().parse(NoName.class).build(); + assertTrue(runtimeOptions.getTagExpressions().isEmpty()); assertTrue(runtimeOptions.getNameFilters().isEmpty()); assertTrue(runtimeOptions.getLineFilters().isEmpty()); } @Test public void create_with_multiple_names() { - RuntimeOptions runtimeOptions = new CucumberOptionsAnnotationParser().parse(MultipleNames.class).build(); + RuntimeOptions runtimeOptions = parser().parse(MultipleNames.class).build(); List filters = runtimeOptions.getNameFilters(); assertEquals(2, filters.size()); @@ -97,9 +103,15 @@ public void create_with_multiple_names() { assertEquals("name2", getRegexpPattern(iterator.next())); } + @Test + public void testObjectFactory() { + RuntimeOptions runtimeOptions = parser().parse(ClassWithCustomObjectFactory.class).build(); + assertEquals(TestObjectFactory.class, runtimeOptions.getObjectFactoryClass()); + } + @Test public void create_with_snippets() { - RuntimeOptions runtimeOptions = new CucumberOptionsAnnotationParser().parse(Snippets.class).build(); + RuntimeOptions runtimeOptions = parser().parse(Snippets.class).build(); assertEquals(SnippetType.CAMELCASE, runtimeOptions.getSnippetType()); } @@ -109,39 +121,32 @@ private String getRegexpPattern(Object pattern) { @Test public void create_default_summary_printer_when_no_summary_printer_plugin_is_defined() { - RuntimeOptions runtimeOptions = new CucumberOptionsAnnotationParser() + RuntimeOptions runtimeOptions = parser() .parse(ClassWithNoSummaryPrinterPlugin.class) .addDefaultSummaryPrinterIfNotPresent() .build(); - Plugins plugins = new Plugins(getClass().getClassLoader(), new PluginFactory(), runtimeOptions); - plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(TimeService.SYSTEM)); - assertPluginExists(plugins.getPlugins(), "cucumber.runtime.formatter.DefaultSummaryPrinter"); + Plugins plugins = new Plugins(new PluginFactory(), runtimeOptions); + plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(Clock.systemUTC())); + assertPluginExists(plugins.getPlugins(), "io.cucumber.core.plugin.DefaultSummaryPrinter"); } @Test public void inherit_plugin_from_baseclass() { - RuntimeOptions runtimeOptions = new CucumberOptionsAnnotationParser().parse(SubClassWithFormatter.class).build(); - Plugins plugins = new Plugins(getClass().getClassLoader(), new PluginFactory(), runtimeOptions); - plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(TimeService.SYSTEM)); + RuntimeOptions runtimeOptions = parser().parse(SubClassWithFormatter.class).build(); + Plugins plugins = new Plugins(new PluginFactory(), runtimeOptions); + plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(Clock.systemUTC())); List pluginList = plugins.getPlugins(); - assertPluginExists(pluginList, "cucumber.runtime.formatter.JSONFormatter"); - assertPluginExists(pluginList, "cucumber.runtime.formatter.PrettyFormatter"); + assertPluginExists(pluginList, "io.cucumber.core.plugin.JSONFormatter"); + assertPluginExists(pluginList, "io.cucumber.core.plugin.PrettyFormatter"); } @Test public void override_monochrome_flag_from_baseclass() { - RuntimeOptions runtimeOptions = new CucumberOptionsAnnotationParser().parse(SubClassWithMonoChromeTrue.class).build(); + RuntimeOptions runtimeOptions = parser().parse(SubClassWithMonoChromeTrue.class).build(); assertTrue(runtimeOptions.isMonochrome()); } - @Test - public void create_with_junit_options() { - RuntimeOptions runtimeOptions = new CucumberOptionsAnnotationParser().parse(ClassWithJunitOption.class).build(); - - assertEquals(asList("option1", "option2=value"), runtimeOptions.getJunitOptions()); - } - private void assertPluginExists(List plugins, String pluginName) { boolean found = false; for (Plugin plugin : plugins) { @@ -154,38 +159,39 @@ private void assertPluginExists(List plugins, String pluginName) { @Test public void create_with_glue() { - RuntimeOptions runtimeOptions = new CucumberOptionsAnnotationParser().parse(ClassWithGlue.class).build(); + RuntimeOptions runtimeOptions = parser().parse(ClassWithGlue.class).build(); assertThat(runtimeOptions.getGlue(), contains(uri("classpath:app/features/user/registration"), uri("classpath:app/features/hooks"))); } @Test public void create_with_extra_glue() { - RuntimeOptions runtimeOptions = new CucumberOptionsAnnotationParser().parse(ClassWithExtraGlue.class).build(); + RuntimeOptions runtimeOptions = parser().parse(ClassWithExtraGlue.class).build(); - assertThat(runtimeOptions.getGlue(), contains(uri("classpath:app/features/hooks"), uri("classpath:cucumber/runtime"))); + assertThat(runtimeOptions.getGlue(), contains(uri("classpath:app/features/hooks"), uri("classpath:io/cucumber/core/options"))); } @Test public void create_with_extra_glue_in_subclass_of_extra_glue() { - RuntimeOptions runtimeOptions = new CucumberOptionsAnnotationParser() + RuntimeOptions runtimeOptions = parser() .parse(SubClassWithExtraGlueOfExtraGlue.class) .build(); - assertThat(runtimeOptions.getGlue(), contains(uri("classpath:app/features/user/hooks"), uri("classpath:app/features/hooks"), uri("classpath:cucumber/runtime"))); + assertThat(runtimeOptions.getGlue(), + contains(uri("classpath:app/features/user/hooks"), uri("classpath:app/features/hooks"), uri("classpath:io/cucumber/core/options"))); } @Test public void create_with_extra_glue_in_subclass_of_glue() { - RuntimeOptions runtimeOptions = new CucumberOptionsAnnotationParser().parse(SubClassWithExtraGlueOfGlue.class).build(); + RuntimeOptions runtimeOptions = parser().parse(SubClassWithExtraGlueOfGlue.class).build(); assertThat(runtimeOptions.getGlue(), contains(uri("classpath:app/features/user/hooks"), uri("classpath:app/features/user/registration"), uri("classpath:app/features/hooks"))); } @Test(expected = CucumberException.class) public void cannot_create_with_glue_and_extra_glue() { - new CucumberOptionsAnnotationParser().parse(ClassWithGlueAndExtraGlue.class).build(); + parser().parse(ClassWithGlueAndExtraGlue.class).build(); } @@ -241,7 +247,12 @@ private static class BaseClassWithMonoChromeFalse { // empty } - @CucumberOptions(plugin = "cucumber.runtime.formatter.AnyStepDefinitionReporter") + @CucumberOptions(objectFactory = TestObjectFactory.class) + private static class ClassWithCustomObjectFactory { + // empty + } + + @CucumberOptions(plugin = "io.cucumber.core.plugin.AnyStepDefinitionReporter") private static class ClassWithNoFormatterPlugin { // empty } @@ -281,4 +292,98 @@ private static class ClassWithGlueAndExtraGlue { // empty } + + private static class CoreCucumberOptions implements CucumberOptionsAnnotationParser.CucumberOptions { + private final CucumberOptions annotation; + + CoreCucumberOptions(CucumberOptions annotation) { + this.annotation = annotation; + } + + @Override + public boolean dryRun() { + return annotation.dryRun(); + } + + @Override + public boolean strict() { + return annotation.strict(); + } + + @Override + public String[] features() { + return annotation.features(); + } + + @Override + public String[] glue() { + return annotation.glue(); + } + + @Override + public String[] extraGlue() { + return annotation.extraGlue(); + } + + @Override + public String[] tags() { + return annotation.tags(); + } + + @Override + public String[] plugin() { + return annotation.plugin(); + } + + @Override + public boolean monochrome() { + return annotation.monochrome(); + } + + @Override + public String[] name() { + return annotation.name(); + } + + @Override + public SnippetType snippets() { + return annotation.snippets(); + } + + @Override + public Class objectFactory() { + return (annotation.objectFactory() == NoObjectFactory.class) ? null : annotation.objectFactory(); + } + } + + private static class CoreCucumberOptionsProvider implements CucumberOptionsAnnotationParser.OptionsProvider { + @Override + public CucumberOptionsAnnotationParser.CucumberOptions getOptions(Class clazz) { + final CucumberOptions annotation = clazz.getAnnotation(CucumberOptions.class); + if (annotation == null) { + return null; + } + return new CoreCucumberOptions(annotation); + } + } + + private static final class TestObjectFactory implements ObjectFactory { + + @Override + public boolean addClass(Class glueClass) { + return false; + } + + @Override + public T getInstance(Class glueClass) { + return null; + } + + @Override + public void start() {} + + @Override + public void stop() {} + + } } diff --git a/core/src/test/java/io/cucumber/core/options/CucumberPropertiesParserTest.java b/core/src/test/java/io/cucumber/core/options/CucumberPropertiesParserTest.java new file mode 100644 index 0000000000..996f5eef89 --- /dev/null +++ b/core/src/test/java/io/cucumber/core/options/CucumberPropertiesParserTest.java @@ -0,0 +1,55 @@ +package io.cucumber.core.options; + +import io.cucumber.core.backend.ObjectFactory; +import org.junit.Test; + +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +import static java.util.Collections.singletonList; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; + +public class CucumberPropertiesParserTest { + + private final CucumberPropertiesParser cucumberPropertiesParser = new CucumberPropertiesParser(); + private final Map properties = new HashMap<>(); + + @Test + public void should_parse_cucumber_options(){ + properties.put(Constants.CUCUMBER_OPTIONS_PROPERTY_NAME, "--glue com.example"); + RuntimeOptions options = cucumberPropertiesParser.parse(properties).build(); + assertThat(options.getGlue(), equalTo(singletonList(URI.create("classpath:com/example")))); + } + + @Test + public void should_parse_cucumber_object_factory(){ + properties.put(Constants.CUCUMBER_OBJECT_FACTORY_PROPERTY_NAME, CustomObjectFactory.class.getName()); + RuntimeOptions options = cucumberPropertiesParser.parse(properties).build(); + assertThat(options.getObjectFactoryClass(), equalTo(CustomObjectFactory.class)); + } + + + private static final class CustomObjectFactory implements ObjectFactory { + @Override + public boolean addClass(Class glueClass) { + return false; + } + + @Override + public T getInstance(Class glueClass) { + return null; + } + + @Override + public void start() { + + } + + @Override + public void stop() { + + } + } +} diff --git a/core/src/test/java/cucumber/runtime/EnvTest.java b/core/src/test/java/io/cucumber/core/options/CucumberPropertiesTest.java similarity index 59% rename from core/src/test/java/cucumber/runtime/EnvTest.java rename to core/src/test/java/io/cucumber/core/options/CucumberPropertiesTest.java index a2a14a8c1a..3ee02e7072 100644 --- a/core/src/test/java/cucumber/runtime/EnvTest.java +++ b/core/src/test/java/io/cucumber/core/options/CucumberPropertiesTest.java @@ -1,18 +1,29 @@ -package cucumber.runtime; +package io.cucumber.core.options; +import org.junit.Before; import org.junit.Test; +import java.util.Collections; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -public class EnvTest { +public class CucumberPropertiesTest { + + private CucumberProperties.CucumberPropertiesMap env = new CucumberProperties.CucumberPropertiesMap(Collections.emptyMap()); - private Env env = new Env("env-test"); + @Before + public void setup(){ + env.put("ENV_TEST","from-bundle"); + env.put("a.b","a.b"); + env.put("B_C","B_C"); + env.put("c.D","C_D"); + } @Test public void looks_up_value_from_environment() { - assertNotNull(env.get("PATH")); + assertNotNull(CucumberProperties.fromEnvironment().get("PATH")); } @Test @@ -25,11 +36,6 @@ public void looks_up_dotted_value_from_resource_bundle_with_dots() { assertEquals("a.b", env.get("a.b")); } - @Test - public void looks_up_dotted_value_from_resource_bundle_with_underscores() { - assertEquals("a.b", env.get("A_B")); - } - @Test public void looks_up_underscored_value_from_resource_bundle_with_dots() { assertEquals("B_C", env.get("b.c")); @@ -41,7 +47,7 @@ public void looks_up_underscored_value_from_resource_bundle_with_underscores() { } @Test - public void looks_up_value_by_exact_case_keuy() { + public void looks_up_value_by_exact_case_key() { assertEquals("C_D", env.get("c.D")); } } diff --git a/core/src/test/java/io/cucumber/core/options/NoObjectFactory.java b/core/src/test/java/io/cucumber/core/options/NoObjectFactory.java new file mode 100644 index 0000000000..45d8ebcfbe --- /dev/null +++ b/core/src/test/java/io/cucumber/core/options/NoObjectFactory.java @@ -0,0 +1,30 @@ +package io.cucumber.core.options; + +import io.cucumber.core.backend.ObjectFactory; + +/** + * This object factory does nothing. It is solely needed for marking purposes. + */ +final class NoObjectFactory implements ObjectFactory { + + private NoObjectFactory() { + // No need for instantiation + } + + @Override + public boolean addClass(Class glueClass) { + return false; + } + + @Override + public T getInstance(Class glueClass) { + return null; + } + + @Override + public void start() {} + + @Override + public void stop() {} + +} diff --git a/core/src/test/java/io/cucumber/core/options/RerunFileTest.java b/core/src/test/java/io/cucumber/core/options/RerunFileTest.java new file mode 100644 index 0000000000..175fe90630 --- /dev/null +++ b/core/src/test/java/io/cucumber/core/options/RerunFileTest.java @@ -0,0 +1,207 @@ +package io.cucumber.core.options; + +import io.cucumber.core.io.Resource; +import io.cucumber.core.io.ResourceLoader; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.URI; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static io.cucumber.core.options.Constants.CUCUMBER_OPTIONS_PROPERTY_NAME; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Collections.*; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.collection.IsCollectionWithSize.hasSize; +import static org.hamcrest.collection.IsIterableContainingInOrder.contains; +import static org.hamcrest.collection.IsMapContaining.hasEntry; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class RerunFileTest { + + @Test + public void loads_features_specified_in_rerun_file() throws Exception { + ResourceLoader resourceLoader = mockFileResource( + "file:path/rerun.txt", + "file:path/bar.feature:2\n" + + "file:path/foo.feature:4\n"); + + RuntimeOptions runtimeOptions = new CommandlineOptionsParser(resourceLoader) + .parse("@file:path/rerun.txt") + .build(); + + assertThat(runtimeOptions.getFeaturePaths(), contains( + URI.create("file:path/bar.feature"), + URI.create("file:path/foo.feature") + )); + assertThat(runtimeOptions.getLineFilters(), hasEntry(URI.create("file:path/bar.feature"), singleton(2))); + assertThat(runtimeOptions.getLineFilters(), hasEntry(URI.create("file:path/foo.feature"), singleton(4))); + } + + @Test + public void loads_no_features_when_rerun_file_is_empty() throws Exception { + ResourceLoader resourceLoader = mockFileResource( + "file:path/rerun.txt", + "" + ); + + RuntimeOptions runtimeOptions = new CommandlineOptionsParser(resourceLoader) + .parse("@file:path/rerun.txt") + .build(); + + assertThat(runtimeOptions.getFeaturePaths(), hasSize(0)); + assertThat(runtimeOptions.getLineFilters(), equalTo(emptyMap())); + } + + @Test + public void loads_no_features_when_rerun_file_contains_new_line() throws Exception { + ResourceLoader resourceLoader = mockFileResource( + "file:path/rerun.txt", + "\n" + ); + + RuntimeOptions runtimeOptions = new CommandlineOptionsParser(resourceLoader) + .parse("@file:path/rerun.txt") + .build(); + + assertThat(runtimeOptions.getFeaturePaths(), hasSize(0)); + assertThat(runtimeOptions.getLineFilters(), equalTo(emptyMap())); + } + + @Test + public void loads_no_features_when_rerun_file_contains_carriage_return() throws Exception { + ResourceLoader resourceLoader = mockFileResource( + "file:path/rerun.txt", + "\r"); + + RuntimeOptions runtimeOptions = new CommandlineOptionsParser(resourceLoader) + .parse("@file:path/rerun.txt") + .build(); + + assertThat(runtimeOptions.getFeaturePaths(), hasSize(0)); + assertThat(runtimeOptions.getLineFilters(), equalTo(emptyMap())); + } + + @Test + public void loads_no_features_when_rerun_file_contains_new_line_and_carriage_return() throws Exception { + ResourceLoader resourceLoader = mockFileResource( + "file:path/rerun.txt", + "\r\n"); + + RuntimeOptions runtimeOptions = new CommandlineOptionsParser(resourceLoader) + .parse("@file:path/rerun.txt") + .build(); + + assertThat(runtimeOptions.getFeaturePaths(), hasSize(0)); + assertThat(runtimeOptions.getLineFilters(), equalTo(emptyMap())); + } + + @Test + public void last_new_line_is_optinal() throws Exception { + ResourceLoader resourceLoader = mockFileResource( + "file:path/rerun.txt", + "file:path/bar.feature:2\npath/foo.feature:4" + ); + + RuntimeOptions runtimeOptions = new CommandlineOptionsParser(resourceLoader) + .parse("@file:path/rerun.txt") + .build(); + + assertThat(runtimeOptions.getFeaturePaths(), contains(URI.create("file:path/bar.feature"), URI.create("file:path/foo.feature"))); + assertThat(runtimeOptions.getLineFilters(), hasEntry(URI.create("file:path/bar.feature"), singleton(2))); + assertThat(runtimeOptions.getLineFilters(), hasEntry(URI.create("file:path/foo.feature"), singleton(4))); + } + + + @Test + public void understands_whitespace_in_rerun_filepath() throws Exception { + ResourceLoader resourceLoader = mockFileResource( + "file:rerun.txt", + "file:/home/users/mp/My%20Documents/tests/bar.feature:2\n"); + + RuntimeOptions runtimeOptions = new CommandlineOptionsParser(resourceLoader) + .parse("@file:rerun.txt") + .build(); + + assertThat(runtimeOptions.getFeaturePaths(), contains(URI.create("file:/home/users/mp/My%20Documents/tests/bar.feature"))); + assertThat(runtimeOptions.getLineFilters(), hasEntry(URI.create("file:/home/users/mp/My%20Documents/tests/bar.feature"), singleton(2))); + } + + + @Test + public void understands_rerun_files_separated_by_with_whitespace() throws Exception { + ResourceLoader resourceLoader = mockFileResource( + "file:path/rerun.txt", + "file:/home/users/mp/My%20Documents/tests/bar.feature:2 file:/home/users/mp/My%20Documents/tests/foo.feature:4"); + + RuntimeOptions runtimeOptions = new CommandlineOptionsParser(resourceLoader) + .parse("@file:path/rerun.txt") + .build(); + + assertThat(runtimeOptions.getFeaturePaths(), contains( + URI.create("file:/home/users/mp/My%20Documents/tests/bar.feature"), + URI.create("file:/home/users/mp/My%20Documents/tests/foo.feature") + )); + + assertThat(runtimeOptions.getLineFilters(), hasEntry(URI.create("file:/home/users/mp/My%20Documents/tests/bar.feature"), singleton(2))); + assertThat(runtimeOptions.getLineFilters(), hasEntry(URI.create("file:/home/users/mp/My%20Documents/tests/foo.feature"), singleton(4))); + } + + @Test + public void understands_rerun_files_without_separation_in_rerun_filepath() throws Exception { + ResourceLoader resourceLoader = mockFileResource( + "file:path/rerun.txt", + "file:/home/users/mp/My%20Documents/tests/bar.feature:2file:/home/users/mp/My%20Documents/tests/foo.feature:4" + ); + + RuntimeOptions runtimeOptions = new CommandlineOptionsParser(resourceLoader) + .parse("@file:path/rerun.txt") + .build(); + + assertThat(runtimeOptions.getFeaturePaths(), contains( + URI.create("file:/home/users/mp/My%20Documents/tests/bar.feature"), + URI.create("file:/home/users/mp/My%20Documents/tests/foo.feature") + )); + + assertThat(runtimeOptions.getLineFilters(), hasEntry(URI.create("file:/home/users/mp/My%20Documents/tests/bar.feature"), singleton(2))); + assertThat(runtimeOptions.getLineFilters(), hasEntry(URI.create("file:/home/users/mp/My%20Documents/tests/foo.feature"), singleton(4))); + } + + @Test + public void clobbers_tag_and_name_filters_from_cli_if_rerun_file_specified_in_cucumber_options_property() throws IOException { + ResourceLoader resourceLoader = mockFileResource( + "file:path/rerun.txt", + "foo.feature:4" + ); + + Map properties = new HashMap<>(); + properties.put(CUCUMBER_OPTIONS_PROPERTY_NAME, "@file:path/rerun.txt"); + + RuntimeOptions options = new CommandlineOptionsParser(resourceLoader) + .parse("--tags", "@should_be_clobbered", "--name", "should_be_clobbered") + .build(); + + RuntimeOptions runtimeOptions = new CucumberPropertiesParser(resourceLoader) + .parse(properties) + .build(options); + + assertEquals(Collections.emptyList(), runtimeOptions.getTagExpressions()); + } + + private ResourceLoader mockFileResource(String path, String contents) throws IOException { + URI uri = URI.create(path); + ResourceLoader resourceLoader = mock(ResourceLoader.class); + Resource resource = mock(Resource.class); + when(resource.getPath()).thenReturn(uri); + when(resource.getInputStream()).thenReturn(new ByteArrayInputStream(contents.getBytes(UTF_8))); + when(resourceLoader.resources(uri, null)).thenReturn(singletonList(resource)); + return resourceLoader; + } + +} diff --git a/core/src/test/java/io/cucumber/core/options/RuntimeOptionsParserTest.java b/core/src/test/java/io/cucumber/core/options/RuntimeOptionsParserTest.java new file mode 100644 index 0000000000..d24d51cdab --- /dev/null +++ b/core/src/test/java/io/cucumber/core/options/RuntimeOptionsParserTest.java @@ -0,0 +1,52 @@ +package io.cucumber.core.options; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Arrays; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.feature.RerunLoader; +import io.cucumber.core.io.MultiLoader; + +final class RuntimeOptionsParserTest { + + private RuntimeOptionsParser parser; + + @BeforeEach + void setUp() throws Exception { + this.parser = new RuntimeOptionsParser(new RerunLoader( new MultiLoader(this.getClass().getClassLoader()))); + } + + @Test + void testParseWithObjectFactoryArgument() { + RuntimeOptionsBuilder optionsBuilder = this.parser.parse(Arrays.asList("--object-factory", TestObjectFactory.class.getName())); + assertNotNull(optionsBuilder); + RuntimeOptions options = optionsBuilder.build(); + assertNotNull(options); + assertEquals(TestObjectFactory.class, options.getObjectFactoryClass()); + } + + + private static final class TestObjectFactory implements ObjectFactory { + + @Override + public boolean addClass(Class glueClass) { + return false; + } + + @Override + public T getInstance(Class glueClass) { + return null; + } + + @Override + public void start() {} + + @Override + public void stop() {} + + } +} diff --git a/core/src/test/java/io/cucumber/core/options/RuntimeOptionsTest.java b/core/src/test/java/io/cucumber/core/options/RuntimeOptionsTest.java index b207122598..5f8af94739 100644 --- a/core/src/test/java/io/cucumber/core/options/RuntimeOptionsTest.java +++ b/core/src/test/java/io/cucumber/core/options/RuntimeOptionsTest.java @@ -1,21 +1,19 @@ package io.cucumber.core.options; -import cucumber.api.Plugin; -import cucumber.api.SnippetType; -import cucumber.api.event.EventListener; -import cucumber.api.event.EventPublisher; -import cucumber.api.formatter.ColorAware; -import cucumber.api.formatter.StrictAware; -import cucumber.runner.TimeService; -import cucumber.runner.TimeServiceEventBus; -import cucumber.runtime.CucumberException; -import cucumber.runtime.Env; -import cucumber.runtime.Shellwords; -import cucumber.runtime.formatter.PluginFactory; -import cucumber.runtime.formatter.Plugins; -import cucumber.runtime.io.Resource; -import cucumber.runtime.io.ResourceLoader; import gherkin.events.PickleEvent; +import io.cucumber.core.plugin.EventListener; +import io.cucumber.core.event.EventPublisher; +import io.cucumber.core.plugin.ColorAware; +import io.cucumber.core.plugin.Plugin; +import io.cucumber.core.plugin.StrictAware; +import io.cucumber.core.snippets.SnippetType; +import io.cucumber.core.exception.CucumberException; +import io.cucumber.core.io.Resource; +import io.cucumber.core.io.ResourceLoader; +import io.cucumber.core.plugin.PluginFactory; +import io.cucumber.core.plugin.Plugins; +import io.cucumber.core.runner.ClockStub; +import io.cucumber.core.runtime.TimeServiceEventBus; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeDiagnosingMatcher; @@ -29,13 +27,11 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URI; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Properties; -import java.util.Set; +import java.time.Clock; +import java.util.*; import java.util.regex.Pattern; +import static io.cucumber.core.options.Constants.CUCUMBER_OPTIONS_PROPERTY_NAME; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; import static java.util.Collections.singleton; @@ -54,7 +50,7 @@ public class RuntimeOptionsTest { - private final Properties properties = new Properties(); + private final Map properties = new HashMap<>(); @Rule public ExpectedException expectedException = ExpectedException.none(); @Rule @@ -142,12 +138,12 @@ public void assigns_filters_from_tags() { RuntimeOptions options = new CommandlineOptionsParser() .parse("--tags", "@keep_this") .build(); - assertThat(options.getTagFilters(), contains("@keep_this")); + assertThat(options.getTagExpressions(), contains("@keep_this")); } @Test public void trims_options() { - assertThat(Shellwords.parse(" --glue somewhere somewhere_else"), + assertThat(ShellWords.parse(" --glue somewhere somewhere_else"), contains("--glue", "somewhere", "somewhere_else")); } @@ -164,10 +160,10 @@ public void creates_html_formatter() { RuntimeOptions options = new CommandlineOptionsParser() .parse("--plugin", "html:target/some/dir", "--glue", "somewhere") .build(); - Plugins plugins = new Plugins(getClass().getClassLoader(), new PluginFactory(), options); - plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(TimeService.SYSTEM)); + Plugins plugins = new Plugins(new PluginFactory(), options); + plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(ClockStub.systemUTC())); - assertThat(plugins.getPlugins().get(0).getClass().getName(), is("cucumber.runtime.formatter.HTMLFormatter")); + assertThat(plugins.getPlugins().get(0).getClass().getName(), is("io.cucumber.core.plugin.HTMLFormatter")); } @Test @@ -176,22 +172,10 @@ public void creates_progress_formatter_as_default() { .parse() .addDefaultFormatterIfNotPresent() .build(); - Plugins plugins = new Plugins(getClass().getClassLoader(), new PluginFactory(), options); - plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(TimeService.SYSTEM)); - assertThat(plugins.getPlugins().get(0).getClass().getName(), is("cucumber.runtime.formatter.ProgressFormatter")); - } - - @Test - public void creates_progress_formatter_when_no_formatter_plugin_is_specified() { - RuntimeOptions options = new CommandlineOptionsParser() - .parse("--plugin", "cucumber.runtime.formatter.AnyStepDefinitionReporter") - .addDefaultFormatterIfNotPresent() - .build(); + Plugins plugins = new Plugins(new PluginFactory(), options); + plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(Clock.systemUTC())); - Plugins plugins = new Plugins(getClass().getClassLoader(), new PluginFactory(), options); - plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(TimeService.SYSTEM)); - - assertThat(plugins.getPlugins(), hasItem(plugin("cucumber.runtime.formatter.ProgressFormatter"))); + assertThat(plugins.getPlugins().get(0).getClass().getName(), is("io.cucumber.core.plugin.ProgressFormatter")); } @Test @@ -200,10 +184,10 @@ public void creates_default_summary_printer_when_no_summary_printer_plugin_is_sp .parse("--plugin", "pretty") .addDefaultSummaryPrinterIfNotPresent() .build(); - Plugins plugins = new Plugins(getClass().getClassLoader(), new PluginFactory(), options); - plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(TimeService.SYSTEM)); + Plugins plugins = new Plugins(new PluginFactory(), options); + plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(Clock.systemUTC())); - assertThat(plugins.getPlugins(), hasItem(plugin("cucumber.runtime.formatter.DefaultSummaryPrinter"))); + assertThat(plugins.getPlugins(), hasItem(plugin("io.cucumber.core.plugin.DefaultSummaryPrinter"))); } @Test @@ -211,11 +195,22 @@ public void creates_null_summary_printer() { RuntimeOptions options = new CommandlineOptionsParser() .parse("--plugin", "null_summary", "--glue", "somewhere") .build(); - Plugins plugins = new Plugins(getClass().getClassLoader(), new PluginFactory(), options); - plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(TimeService.SYSTEM)); + Plugins plugins = new Plugins(new PluginFactory(), options); + plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(Clock.systemUTC())); - assertThat(plugins.getPlugins(), hasItem(plugin("cucumber.runtime.formatter.NullSummaryPrinter"))); - assertThat(plugins.getPlugins(), not(hasItem(plugin("cucumber.runtime.formatter.DefaultSummaryPrinter")))); + assertThat(plugins.getPlugins(), hasItem(plugin("io.cucumber.core.plugin.NullSummaryPrinter"))); + assertThat(plugins.getPlugins(), not(hasItem(plugin("io.cucumber.core.plugin.DefaultSummaryPrinter")))); + } + + @Test + public void replaces_incompatible_intellij_idea_plugin() { + RuntimeOptions options = new CommandlineOptionsParser() + .parse("--plugin", "org.jetbrains.plugins.cucumber.java.run.CucumberJvm3SMFormatter") + .build(); + Plugins plugins = new Plugins(new PluginFactory(), options); + plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(Clock.systemUTC())); + + assertThat(plugins.getPlugins(), not(hasItem(plugin("io.cucumber.core.plugin.PrettyPrinter")))); } @Test @@ -286,9 +281,9 @@ public void name_with_spaces_is_preserved() { @Test public void ensure_name_with_spaces_works_with_cucumber_options() { - properties.setProperty("cucumber.options", "--name 'some Name'"); - RuntimeOptions options = new EnvironmentOptionsParser() - .parse(new Env(properties)) + properties.put(CUCUMBER_OPTIONS_PROPERTY_NAME, "--name 'some Name'"); + RuntimeOptions options = new CucumberPropertiesParser() + .parse(properties) .build(); Pattern actualPattern = options.getNameFilters().iterator().next(); assertThat(actualPattern.pattern(), is("some Name")); @@ -296,50 +291,18 @@ public void ensure_name_with_spaces_works_with_cucumber_options() { @Test public void ensure_name_with_spaces_works_with_args() { - assertThat(Shellwords.parse("--name 'some Name'"), contains("--name", "some Name")); - } - - @Test - public void assigns_single_junit_option() { - RuntimeOptions options = new CommandlineOptionsParser() - .parse("--junit,option") - .build(); - assertThat(options.getJunitOptions(), contains("option")); - } - - @Test - public void assigns_multiple_junit_options() { - RuntimeOptions options = new CommandlineOptionsParser() - .parse("--junit,option1,option2=value") - .build(); - assertThat(options.getJunitOptions(), contains("option1", "option2=value")); - } - - @Test - public void clobbers_junit_options_from_cli_if_junit_options_specified_in_cucumber_options_property() { - properties.setProperty("cucumber.options", "--junit,option_from_property"); - RuntimeOptions runtimeOptions = new CommandlineOptionsParser() - .parse("--junit,option_to_be_clobbered") - .build(); - RuntimeOptions options = new EnvironmentOptionsParser() - .parse(new Env(properties)) - .build(runtimeOptions); - assertThat(options.getJunitOptions(), contains("option_from_property")); + assertThat(ShellWords.parse("--name 'some Name'"), contains("--name", "some Name")); } @Test public void overrides_options_with_system_properties_without_clobbering_non_overridden_ones() { - - Properties properties = new Properties(); - properties.setProperty("cucumber.options", "--glue lookatme this_clobbers_feature_paths"); - Env env = new Env(properties); - + properties.put(CUCUMBER_OPTIONS_PROPERTY_NAME, "--glue lookatme this_clobbers_feature_paths"); RuntimeOptions runtimeOptions = new CommandlineOptionsParser() .parse("--strict", "--glue", "somewhere", "somewhere_else") .build(); - RuntimeOptions options = new EnvironmentOptionsParser() - .parse(env) + RuntimeOptions options = new CucumberPropertiesParser() + .parse(properties) .build(runtimeOptions); assertThat(options.getFeaturePaths(), contains(uri("file:this_clobbers_feature_paths"))); assertThat(options.getGlue(), contains(uri("classpath:lookatme"))); @@ -348,87 +311,88 @@ public void overrides_options_with_system_properties_without_clobbering_non_over @Test public void ensure_cli_glue_is_preserved_when_cucumber_options_property_defined() { - properties.setProperty("cucumber.options", "--tags @foo"); + properties.put(CUCUMBER_OPTIONS_PROPERTY_NAME, "--tags @foo"); RuntimeOptions runtimeOptions = new CommandlineOptionsParser() .parse(asList("--glue", "somewhere")) .build(); - RuntimeOptions options = new EnvironmentOptionsParser() - .parse(new Env(properties)) + RuntimeOptions options = new CucumberPropertiesParser() + .parse(properties) .build(runtimeOptions); assertThat(options.getGlue(), contains(uri("classpath:somewhere"))); } @Test public void clobbers_filters_from_cli_if_filters_specified_in_cucumber_options_property() { - properties.setProperty("cucumber.options", "--tags @clobber_with_this"); + properties.put(CUCUMBER_OPTIONS_PROPERTY_NAME, "--tags @clobber_with_this"); RuntimeOptions runtimeOptions = new CommandlineOptionsParser() .parse(asList("--tags", "@should_be_clobbered")) .build(); - RuntimeOptions options = new EnvironmentOptionsParser() - .parse(new Env(properties)) + RuntimeOptions options = new CucumberPropertiesParser() + .parse(properties) .build(runtimeOptions); - assertThat(options.getTagFilters(), contains("@clobber_with_this")); + assertThat(options.getTagExpressions(), contains("@clobber_with_this")); } @Test public void clobbers_tag_and_name_filters_from_cli_if_line_filters_specified_in_cucumber_options_property() { - properties.setProperty("cucumber.options", "path/file.feature:3"); + properties.put(CUCUMBER_OPTIONS_PROPERTY_NAME, "path/file.feature:3"); RuntimeOptions runtimeOptions = new CommandlineOptionsParser() .parse("--tags", "@should_be_clobbered", "--name", "should_be_clobbered") .build(); - RuntimeOptions options = new EnvironmentOptionsParser() - .parse(new Env(properties)) + RuntimeOptions options = new CucumberPropertiesParser() + .parse(properties) .build(runtimeOptions); - assertThat(options.getTagFilters(), emptyCollectionOf(String.class)); + assertThat(options.getTagExpressions(), emptyCollectionOf(String.class)); } @Test public void clobbers_tag_and_name_filters_from_cli_if_rerun_file_specified_in_cucumber_options_property() { - properties.setProperty("cucumber.options", "@src/test/resources/cucumber/runtime/runtime-options-rerun.txt"); + properties.put(CUCUMBER_OPTIONS_PROPERTY_NAME, "@src/test/resources/io/cucumber/core/options/runtime-options-rerun.txt"); RuntimeOptions runtimeOptions = new CommandlineOptionsParser() .parse("--tags", "@should_be_clobbered", "--name", "should_be_clobbered") .build(); - RuntimeOptions options = new EnvironmentOptionsParser() - .parse(new Env(properties)) + RuntimeOptions options = new CucumberPropertiesParser() + .parse(properties) .build(runtimeOptions); - assertThat(options.getTagFilters(), emptyCollectionOf(String.class)); + + assertThat(options.getTagExpressions(), emptyCollectionOf(String.class)); assertThat(options.getLineFilters(), hasEntry(uri("file:this/should/be/rerun.feature"), singleton(12))); } @Test public void preserves_filters_from_cli_if_filters_not_specified_in_cucumber_options_property() { - properties.setProperty("cucumber.options", "--strict"); + properties.put(CUCUMBER_OPTIONS_PROPERTY_NAME, "--strict"); RuntimeOptions runtimeOptions = new CommandlineOptionsParser() .parse(asList("--tags", "@keep_this")) .build(); - RuntimeOptions options = new EnvironmentOptionsParser() - .parse(new Env(properties)) + RuntimeOptions options = new CucumberPropertiesParser() + .parse(properties) .build(runtimeOptions); - assertThat(options.getTagFilters(), contains("@keep_this")); + assertThat(options.getTagExpressions(), contains("@keep_this")); } @Test public void clobbers_features_from_cli_if_features_specified_in_cucumber_options_property() { - properties.setProperty("cucumber.options", "new newer"); + properties.put(CUCUMBER_OPTIONS_PROPERTY_NAME, "new newer"); RuntimeOptions runtimeOptions = new CommandlineOptionsParser() .parse(asList("old", "older")) .build(); - RuntimeOptions options = new EnvironmentOptionsParser() - .parse(new Env(properties)) + RuntimeOptions options = new CucumberPropertiesParser() + .parse(properties) .build(runtimeOptions); assertThat(options.getFeaturePaths(), contains(uri("file:new"), uri("file:newer"))); } @Test public void strips_lines_from_features_from_cli_if_filters_are_specified_in_cucumber_options_property() { - properties.setProperty("cucumber.options", "--tags @Tag"); + properties.put(CUCUMBER_OPTIONS_PROPERTY_NAME, "--tags @Tag"); RuntimeOptions runtimeOptions = new CommandlineOptionsParser() .parse("path/file.feature:3") .build(); - RuntimeOptions options = new EnvironmentOptionsParser() - .parse(new Env(properties)) + RuntimeOptions options = new CucumberPropertiesParser() + .parse(properties) .build(runtimeOptions); assertThat(options.getFeaturePaths(), contains(uri("file:path/file.feature"))); } @@ -436,7 +400,7 @@ public void strips_lines_from_features_from_cli_if_filters_are_specified_in_cucu @Test public void strips_lines_from_rerun_file_from_cli_if_filters_are_specified_in_cucumber_options_property() throws IOException { - properties.setProperty("cucumber.options", "--tags @Tag"); + properties.put(CUCUMBER_OPTIONS_PROPERTY_NAME, "--tags @Tag"); String rerunPath = "file:path/rerun.txt"; String rerunFile = "file:path/file.feature:3\n"; mockFileResource(resourceLoader, rerunPath, rerunFile); @@ -448,12 +412,12 @@ public void strips_lines_from_rerun_file_from_cli_if_filters_are_specified_in_cu @Test public void preserves_features_from_cli_if_features_not_specified_in_cucumber_options_property() { - properties.setProperty("cucumber.options", "--plugin pretty"); + properties.put(CUCUMBER_OPTIONS_PROPERTY_NAME, "--plugin pretty"); RuntimeOptions runtimeOptions = new CommandlineOptionsParser() .parse(asList("old", "older")) .build(); - RuntimeOptions options = new EnvironmentOptionsParser() - .parse(new Env(properties)) + RuntimeOptions options = new CucumberPropertiesParser() + .parse(properties) .build(runtimeOptions); assertThat(options.getFeaturePaths(), contains(uri("file:old"), uri("file:older"))); @@ -461,110 +425,116 @@ public void preserves_features_from_cli_if_features_not_specified_in_cucumber_op @Test public void clobbers_line_filters_from_cli_if_features_specified_in_cucumber_options_property() { - properties.setProperty("cucumber.options", "new newer"); + properties.put(CUCUMBER_OPTIONS_PROPERTY_NAME, "new newer"); RuntimeOptions runtimeOptions = new CommandlineOptionsParser() .parse(asList("--tags", "@keep_this", "path/file1.feature:1")) .build(); - RuntimeOptions options = new EnvironmentOptionsParser() - .parse(new Env(properties)) + RuntimeOptions options = new CucumberPropertiesParser() + .parse(properties) .build(runtimeOptions); assertThat(options.getFeaturePaths(), contains(uri("file:new"), uri("file:newer"))); - assertThat(options.getTagFilters(), contains("@keep_this")); + assertThat(options.getTagExpressions(), contains("@keep_this")); } @Test public void clobbers_formatter_plugins_from_cli_if_formatters_specified_in_cucumber_options_property() { - properties.setProperty("cucumber.options", "--plugin pretty"); + properties.put(CUCUMBER_OPTIONS_PROPERTY_NAME, "--plugin pretty"); + RuntimeOptions runtimeOptions = new CommandlineOptionsParser() .parse("--plugin", "html:target/some/dir", "--glue", "somewhere") .build(); - RuntimeOptions options = new EnvironmentOptionsParser() - .parse(new Env(properties)) + RuntimeOptions options = new CucumberPropertiesParser() + .parse(properties) .build(runtimeOptions); - Plugins plugins = new Plugins(getClass().getClassLoader(), new PluginFactory(), options); - plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(TimeService.SYSTEM)); + Plugins plugins = new Plugins(new PluginFactory(), options); + plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(Clock.systemUTC())); + - assertThat(plugins.getPlugins(), hasItem(plugin("cucumber.runtime.formatter.PrettyFormatter"))); - assertThat(plugins.getPlugins(), not(hasItem(plugin("cucumber.runtime.formatter.HTMLFormatter")))); + assertThat(plugins.getPlugins(), hasItem(plugin("io.cucumber.core.plugin.PrettyFormatter"))); + assertThat(plugins.getPlugins(), not(hasItem(plugin("io.cucumber.core.plugin.HTMLFormatter")))); } @Test public void adds_to_formatter_plugins_with_add_plugin_option() { - properties.setProperty("cucumber.options", "--add-plugin pretty"); + properties.put(CUCUMBER_OPTIONS_PROPERTY_NAME, "--add-plugin pretty"); + RuntimeOptions runtimeOptions = new CommandlineOptionsParser() .parse("--plugin", "html:target/some/dir", "--glue", "somewhere") .build(); - RuntimeOptions options = new EnvironmentOptionsParser() - .parse(new Env(properties)) + RuntimeOptions options = new CucumberPropertiesParser() + .parse(properties) .build(runtimeOptions); - Plugins plugins = new Plugins(getClass().getClassLoader(), new PluginFactory(), options); - plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(TimeService.SYSTEM)); + Plugins plugins = new Plugins(new PluginFactory(), options); + plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(Clock.systemUTC())); + - assertThat(plugins.getPlugins(), hasItem(plugin("cucumber.runtime.formatter.HTMLFormatter"))); - assertThat(plugins.getPlugins(), hasItem(plugin("cucumber.runtime.formatter.PrettyFormatter"))); + assertThat(plugins.getPlugins(), hasItem(plugin("io.cucumber.core.plugin.HTMLFormatter"))); + assertThat(plugins.getPlugins(), hasItem(plugin("io.cucumber.core.plugin.PrettyFormatter"))); } @Test public void clobbers_summary_plugins_from_cli_if_summary_printer_specified_in_cucumber_options_property() { - properties.setProperty("cucumber.options", "--plugin default_summary"); + properties.put(CUCUMBER_OPTIONS_PROPERTY_NAME, "--plugin default_summary"); RuntimeOptions runtimeOptions = new CommandlineOptionsParser() .parse("--plugin", "null_summary", "--glue", "somewhere") .build(); - RuntimeOptions options = new EnvironmentOptionsParser() - .parse(new Env(properties)) + RuntimeOptions options = new CucumberPropertiesParser() + .parse(properties) .build(runtimeOptions); - Plugins plugins = new Plugins(getClass().getClassLoader(), new PluginFactory(), options); - plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(TimeService.SYSTEM)); + Plugins plugins = new Plugins(new PluginFactory(), options); + plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(Clock.systemUTC())); - assertThat(plugins.getPlugins(), hasItem(plugin("cucumber.runtime.formatter.DefaultSummaryPrinter"))); - assertThat(plugins.getPlugins(), not(hasItem(plugin("cucumber.runtime.formatter.NullSummaryPrinter")))); + assertThat(plugins.getPlugins(), hasItem(plugin("io.cucumber.core.plugin.DefaultSummaryPrinter"))); + assertThat(plugins.getPlugins(), not(hasItem(plugin("io.cucumber.core.plugin.NullSummaryPrinter")))); } @Test public void adds_to_summary_plugins_with_add_plugin_option() { - properties.setProperty("cucumber.options", "--add-plugin default_summary"); + properties.put(CUCUMBER_OPTIONS_PROPERTY_NAME, "--add-plugin default_summary"); RuntimeOptions runtimeOptions = new CommandlineOptionsParser() .parse("--plugin", "null_summary", "--glue", "somewhere") .build(); - RuntimeOptions options = new EnvironmentOptionsParser() - .parse(new Env(properties)) + RuntimeOptions options = new CucumberPropertiesParser() + .parse(properties) .build(runtimeOptions); - Plugins plugins = new Plugins(getClass().getClassLoader(), new PluginFactory(), options); - plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(TimeService.SYSTEM)); + Plugins plugins = new Plugins(new PluginFactory(), options); + plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(Clock.systemUTC())); - assertThat(plugins.getPlugins(), hasItem(plugin("cucumber.runtime.formatter.NullSummaryPrinter"))); - assertThat(plugins.getPlugins(), hasItem(plugin("cucumber.runtime.formatter.DefaultSummaryPrinter"))); + assertThat(plugins.getPlugins(), hasItem(plugin("io.cucumber.core.plugin.NullSummaryPrinter"))); + assertThat(plugins.getPlugins(), hasItem(plugin("io.cucumber.core.plugin.DefaultSummaryPrinter"))); } @Test public void does_not_clobber_plugins_of_different_type_when_specifying_plugins_in_cucumber_options_property() { - properties.setProperty("cucumber.options", "--plugin default_summary"); + properties.put(CUCUMBER_OPTIONS_PROPERTY_NAME, "--plugin default_summary"); + RuntimeOptions runtimeOptions = new CommandlineOptionsParser() .parse("--plugin", "pretty", "--glue", "somewhere") .build(); - RuntimeOptions options = new EnvironmentOptionsParser() - .parse(new Env(properties)) + RuntimeOptions options = new CucumberPropertiesParser() + .parse(properties) .build(runtimeOptions); - Plugins plugins = new Plugins(getClass().getClassLoader(), new PluginFactory(), options); - plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(TimeService.SYSTEM)); + Plugins plugins = new Plugins(new PluginFactory(), options); + plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(Clock.systemUTC())); + - assertThat(plugins.getPlugins(), hasItem(plugin("cucumber.runtime.formatter.PrettyFormatter"))); - assertThat(plugins.getPlugins(), hasItem(plugin("cucumber.runtime.formatter.DefaultSummaryPrinter"))); + assertThat(plugins.getPlugins(), hasItem(plugin("io.cucumber.core.plugin.PrettyFormatter"))); + assertThat(plugins.getPlugins(), hasItem(plugin("io.cucumber.core.plugin.DefaultSummaryPrinter"))); } @Test public void allows_removal_of_strict_in_cucumber_options_property() { - properties.setProperty("cucumber.options", "--no-strict"); + properties.put(CUCUMBER_OPTIONS_PROPERTY_NAME, "--no-strict"); RuntimeOptions runtimeOptions = new CommandlineOptionsParser() .parse("--strict") .build(); - RuntimeOptions options = new EnvironmentOptionsParser() - .parse(new Env(properties)) + RuntimeOptions options = new CucumberPropertiesParser() + .parse(properties) .build(runtimeOptions); assertThat(options.isStrict(), is(false)); } @@ -580,7 +550,7 @@ public void fail_on_unsupported_options() { @Test public void threads_default_1() { RuntimeOptions options = new CommandlineOptionsParser() - .parse(Collections.emptyList()) + .parse(Collections.emptyList()) .build(); assertThat(options.getThreads(), is(1)); } @@ -607,8 +577,8 @@ public void set_monochrome_on_color_aware_formatters() { RuntimeOptions options = new CommandlineOptionsParser() .parse("--monochrome", "--plugin", AwareFormatter.class.getName()) .build(); - Plugins plugins = new Plugins(getClass().getClassLoader(), new PluginFactory(), options); - plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(TimeService.SYSTEM)); + Plugins plugins = new Plugins(new PluginFactory(), options); + plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(Clock.systemUTC())); AwareFormatter formatter = (AwareFormatter) plugins.getPlugins().get(0); assertThat(formatter.isMonochrome(), is(true)); @@ -619,8 +589,8 @@ public void set_strict_on_strict_aware_formatters() { RuntimeOptions options = new CommandlineOptionsParser() .parse("--strict", "--plugin", AwareFormatter.class.getName()) .build(); - Plugins plugins = new Plugins(getClass().getClassLoader(), new PluginFactory(), options); - plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(TimeService.SYSTEM)); + Plugins plugins = new Plugins(new PluginFactory(), options); + plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(Clock.systemUTC())); AwareFormatter formatter = (AwareFormatter) plugins.getPlugins().get(0); assertThat(formatter.isStrict(), is(true)); @@ -630,22 +600,22 @@ public void set_strict_on_strict_aware_formatters() { @Test public void ensure_default_snippet_type_is_underscore() { RuntimeOptions runtimeOptions = new CommandlineOptionsParser() - .parse(Collections.emptyList()) + .parse(Collections.emptyList()) .build(); - RuntimeOptions options = new EnvironmentOptionsParser() - .parse(new Env(properties)) + RuntimeOptions options = new CucumberPropertiesParser() + .parse(properties) .build(runtimeOptions); assertThat(options.getSnippetType(), is(SnippetType.UNDERSCORE)); } @Test public void set_snippet_type() { - properties.setProperty("cucumber.options", "--snippets camelcase"); + properties.put(CUCUMBER_OPTIONS_PROPERTY_NAME, "--snippets camelcase"); RuntimeOptions runtimeOptions = new CommandlineOptionsParser() - .parse(Collections.emptyList()) + .parse(Collections.emptyList()) .build(); - RuntimeOptions options = new EnvironmentOptionsParser() - .parse(new Env(properties)) + RuntimeOptions options = new CucumberPropertiesParser() + .parse(properties) .build(runtimeOptions); assertThat(options.getSnippetType(), is(SnippetType.CAMELCASE)); } @@ -736,7 +706,7 @@ public void loads_features_specified_in_rerun_file() throws Exception { @Test public void loads_features_specified_in_rerun_file_with_empty_cucumber_options() throws Exception { - properties.put("cucumber.options", ""); + properties.put(CUCUMBER_OPTIONS_PROPERTY_NAME, ""); String rerunPath = "file:path/rerun.txt"; String rerunFile = "file:path/bar.feature:2\n"; mockFileResource(resourceLoader, rerunPath, rerunFile); @@ -750,7 +720,7 @@ public void loads_features_specified_in_rerun_file_with_empty_cucumber_options() @Test public void clobbers_features_from_rerun_file_specified_in_cli_if_features_specified_in_cucumber_options_property() throws Exception { - properties.put("cucumber.options", "file:path/foo.feature"); + properties.put(CUCUMBER_OPTIONS_PROPERTY_NAME, "file:path/foo.feature"); String rerunPath = "file:path/rerun.txt"; String rerunFile = "file:path/bar.feature:2\n"; mockFileResource(resourceLoader, rerunPath, rerunFile); @@ -759,8 +729,8 @@ public void clobbers_features_from_rerun_file_specified_in_cli_if_features_speci .parse("@" + rerunPath) .build(); - RuntimeOptions options = new EnvironmentOptionsParser(resourceLoader) - .parse(new Env(properties)) + RuntimeOptions options = new CucumberPropertiesParser(resourceLoader) + .parse(properties) .build(runtimeOptions); assertThat(options.getFeaturePaths(), contains(uri("file:path/foo.feature"))); @@ -782,13 +752,13 @@ public void loads_no_features_when_rerun_file_is_empty() throws Exception { @Test - public void loads_no_features_when_rerun_file_specified_in_cucumber_options_property_is_empty() throws Exception { - properties.setProperty("cucumber.options", "@src/test/resources/cucumber/runtime/runtime-options-empty-rerun.txt"); + public void loads_no_features_when_rerun_file_specified_in_cucumber_options_property_is_empty() { + properties.put(CUCUMBER_OPTIONS_PROPERTY_NAME, "@src/test/resources/io/cucumber/core/options/runtime-options-empty-rerun.txt"); RuntimeOptions runtimeOptions = new CommandlineOptionsParser() .parse(singletonList("src/test/resources/cucumber/runtime/formatter")) .build(); - RuntimeOptions options = new EnvironmentOptionsParser() - .parse(new Env(properties)) + RuntimeOptions options = new CucumberPropertiesParser() + .parse(properties) .build(runtimeOptions); assertThat(options.getFeaturePaths(), emptyCollectionOf(URI.class)); } diff --git a/core/src/test/java/cucumber/runtime/ShellwordsTest.java b/core/src/test/java/io/cucumber/core/options/ShellWordsTest.java similarity index 61% rename from core/src/test/java/cucumber/runtime/ShellwordsTest.java rename to core/src/test/java/io/cucumber/core/options/ShellWordsTest.java index 8640cd3d40..901842e19c 100644 --- a/core/src/test/java/cucumber/runtime/ShellwordsTest.java +++ b/core/src/test/java/io/cucumber/core/options/ShellWordsTest.java @@ -1,30 +1,28 @@ -package cucumber.runtime; +package io.cucumber.core.options; -import org.junit.Ignore; import org.junit.Test; import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; -public class ShellwordsTest { - +public class ShellWordsTest { @Test public void parses_single_quoted_strings() { - assertEquals(asList("--name", "The Fox"), Shellwords.parse("--name 'The Fox'")); + assertEquals(asList("--name", "The Fox"), ShellWords.parse("--name 'The Fox'")); } @Test public void parses_double_quoted_strings() { - assertEquals(asList("--name", "The Fox"), Shellwords.parse("--name \"The Fox\"")); + assertEquals(asList("--name", "The Fox"), ShellWords.parse("--name \"The Fox\"")); } @Test public void parses_both_single_and_double_quoted_strings() { - assertEquals(asList("--name", "The Fox", "--fur", "Brown White"), Shellwords.parse("--name \"The Fox\" --fur 'Brown White'")); + assertEquals(asList("--name", "The Fox", "--fur", "Brown White"), ShellWords.parse("--name \"The Fox\" --fur 'Brown White'")); } @Test public void can_quote_both_single_and_double_quotes() { - assertEquals(asList("'", "\""), Shellwords.parse("\"'\" '\"'")); + assertEquals(asList("'", "\""), ShellWords.parse("\"'\" '\"'")); } } diff --git a/core/src/test/java/io/cucumber/core/options/TestPluginOption.java b/core/src/test/java/io/cucumber/core/options/TestPluginOption.java new file mode 100644 index 0000000000..357fc77cbb --- /dev/null +++ b/core/src/test/java/io/cucumber/core/options/TestPluginOption.java @@ -0,0 +1,10 @@ +package io.cucumber.core.options; + + +public class TestPluginOption { + + public static PluginOption parse(String pluginArgumentPattern){ + return PluginOption.parse(pluginArgumentPattern); + } + +} \ No newline at end of file diff --git a/core/src/test/java/cucumber/api/event/CanonicalEventOrderTest.java b/core/src/test/java/io/cucumber/core/plugin/CanonicalEventOrderTest.java similarity index 85% rename from core/src/test/java/cucumber/api/event/CanonicalEventOrderTest.java rename to core/src/test/java/io/cucumber/core/plugin/CanonicalEventOrderTest.java index 22d9bac4c5..00d5d4bab6 100644 --- a/core/src/test/java/cucumber/api/event/CanonicalEventOrderTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/CanonicalEventOrderTest.java @@ -1,12 +1,17 @@ -package cucumber.api.event; - -import cucumber.api.TestCase; -import gherkin.pickles.PickleLocation; +package io.cucumber.core.plugin; + +import io.cucumber.core.event.Event; +import io.cucumber.core.event.SnippetsSuggestedEvent; +import io.cucumber.core.event.TestCase; +import io.cucumber.core.event.TestCaseStarted; +import io.cucumber.core.event.TestRunFinished; +import io.cucumber.core.event.TestRunStarted; +import io.cucumber.core.event.TestSourceRead; import org.junit.Test; +import java.time.Instant; import java.util.Arrays; import java.util.Collections; -import java.util.Date; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -21,29 +26,25 @@ public class CanonicalEventOrderTest { private CanonicalEventOrder comparator = new CanonicalEventOrder(); - static long getTime() { - return new Date().getTime(); - } - - private static long getTimeStampMillis() { - return System.currentTimeMillis(); + private static Instant getInstant() { + return Instant.now(); } - static Event createTestCaseEvent(final String uri, final int line) { + private static Event createTestCaseEvent(final String uri, final int line) { final TestCase testCase = mock(TestCase.class); given(testCase.getUri()).willReturn(uri); given(testCase.getLine()).willReturn(line); - return new TestCaseStarted(getTime(), getTimeStampMillis(), testCase); + return new TestCaseStarted(getInstant(), testCase); } - private Event runStarted = new TestRunStarted(getTime(), getTimeStampMillis()); - private Event testRead = new TestSourceRead(getTime(), getTimeStampMillis(), "uri", "source"); - private Event suggested = new SnippetsSuggestedEvent(getTime(), getTimeStampMillis(), "uri", Collections.emptyList(), Collections.emptyList()); + private Event runStarted = new TestRunStarted(getInstant()); + private Event testRead = new TestSourceRead(getInstant(), "uri", "source"); + private Event suggested = new SnippetsSuggestedEvent(getInstant(), "uri", Collections.emptyList(), Collections.emptyList()); private Event feature1Case1Started = createTestCaseEvent("feature1", 1); private Event feature1Case2Started = createTestCaseEvent("feature1", 9); private Event feature1Case3Started = createTestCaseEvent("feature1", 11); private Event feature2Case1Started = createTestCaseEvent("feature2", 1); - private Event runFinished = new TestRunFinished(getTime(), getTimeStampMillis()); + private Event runFinished = new TestRunFinished(getInstant()); @Test public void verifyTestRunStartedSortedCorrectly() { diff --git a/core/src/test/java/cucumber/runtime/formatter/FormatterBuilder.java b/core/src/test/java/io/cucumber/core/plugin/FormatterBuilder.java similarity index 80% rename from core/src/test/java/cucumber/runtime/formatter/FormatterBuilder.java rename to core/src/test/java/io/cucumber/core/plugin/FormatterBuilder.java index 0b2d3880fd..e3db441bd1 100644 --- a/core/src/test/java/cucumber/runtime/formatter/FormatterBuilder.java +++ b/core/src/test/java/io/cucumber/core/plugin/FormatterBuilder.java @@ -1,4 +1,4 @@ -package cucumber.runtime.formatter; +package io.cucumber.core.plugin; public class FormatterBuilder { diff --git a/core/src/test/java/cucumber/runtime/formatter/FormatterSpy.java b/core/src/test/java/io/cucumber/core/plugin/FormatterSpy.java similarity index 83% rename from core/src/test/java/cucumber/runtime/formatter/FormatterSpy.java rename to core/src/test/java/io/cucumber/core/plugin/FormatterSpy.java index 06612a5598..027557c20c 100644 --- a/core/src/test/java/cucumber/runtime/formatter/FormatterSpy.java +++ b/core/src/test/java/io/cucumber/core/plugin/FormatterSpy.java @@ -1,13 +1,12 @@ -package cucumber.runtime.formatter; +package io.cucumber.core.plugin; -import cucumber.api.event.EventHandler; -import cucumber.api.event.EventListener; -import cucumber.api.event.EventPublisher; -import cucumber.api.event.TestCaseFinished; -import cucumber.api.event.TestCaseStarted; -import cucumber.api.event.TestRunFinished; -import cucumber.api.event.TestStepFinished; -import cucumber.api.event.TestStepStarted; +import io.cucumber.core.event.EventHandler; +import io.cucumber.core.event.EventPublisher; +import io.cucumber.core.event.TestCaseFinished; +import io.cucumber.core.event.TestCaseStarted; +import io.cucumber.core.event.TestRunFinished; +import io.cucumber.core.event.TestStepFinished; +import io.cucumber.core.event.TestStepStarted; public class FormatterSpy implements EventListener { StringBuilder calls = new StringBuilder(); diff --git a/core/src/test/java/cucumber/runtime/formatter/HTMLFormatterTest.java b/core/src/test/java/io/cucumber/core/plugin/HTMLFormatterTest.java similarity index 85% rename from core/src/test/java/cucumber/runtime/formatter/HTMLFormatterTest.java rename to core/src/test/java/io/cucumber/core/plugin/HTMLFormatterTest.java index 35fd6b597e..680348374d 100644 --- a/core/src/test/java/cucumber/runtime/formatter/HTMLFormatterTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/HTMLFormatterTest.java @@ -1,21 +1,22 @@ -package cucumber.runtime.formatter; - -import cucumber.api.Result; -import cucumber.api.formatter.NiceAppendable; -import cucumber.runner.TestHelper; -import cucumber.runtime.Utils; -import cucumber.runtime.model.CucumberFeature; -import cucumber.util.FixJava; +package io.cucumber.core.plugin; + import gherkin.deps.com.google.gson.JsonParser; +import io.cucumber.core.event.Result; +import io.cucumber.core.feature.CucumberFeature; +import io.cucumber.core.runner.TestHelper; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.junit.Test; import org.mockito.stubbing.Answer; +import java.io.BufferedReader; import java.io.File; +import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; +import java.time.Duration; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; import java.util.HashMap; @@ -24,16 +25,21 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; - -import static cucumber.runner.TestHelper.createEmbedHookAction; -import static cucumber.runner.TestHelper.createWriteHookAction; -import static cucumber.runner.TestHelper.feature; -import static cucumber.runner.TestHelper.result; +import java.util.stream.Collectors; + +import static io.cucumber.core.runner.TestHelper.createEmbedHookAction; +import static io.cucumber.core.runner.TestHelper.createWriteHookAction; +import static io.cucumber.core.runner.TestHelper.feature; +import static io.cucumber.core.runner.TestHelper.result; +import static java.nio.charset.StandardCharsets.US_ASCII; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.time.Duration.ofMillis; import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; + public class HTMLFormatterTest { private final static String jsFunctionCallRegexString = "formatter.(\\w*)\\(([^)]*)\\);"; private final static Pattern jsFunctionCallRegex = Pattern.compile(jsFunctionCallRegexString); @@ -44,12 +50,12 @@ public class HTMLFormatterTest { private final List> hooks = new ArrayList<>(); private final List hookLocations = new ArrayList<>(); private final List> hookActions = new ArrayList<>(); - private Long stepDuration = null; + private Duration stepDuration = null; private URL outputDir; - public void writeReport() throws Throwable { - outputDir = Utils.toURL(TempDir.createTempDirectory().getAbsolutePath()); + private void writeReport() throws Throwable { + outputDir = TempDir.createTempDirectory().toURI().toURL(); runFeaturesWithFormatter(outputDir); } @@ -57,7 +63,7 @@ public void writeReport() throws Throwable { public void writes_index_html() throws Throwable { writeReport(); URL indexHtml = new URL(outputDir, "index.html"); - Document document = Jsoup.parse(new File(indexHtml.getFile()), "UTF-8"); + Document document = Jsoup.parse(new File(indexHtml.getFile()), UTF_8.name()); Element reportElement = document.body().getElementsByClass("cucumber-report").first(); assertEquals("", reportElement.text()); } @@ -65,68 +71,66 @@ public void writes_index_html() throws Throwable { @Test public void writes_valid_report_js() throws Throwable { writeReport(); - String reportJs = FixJava.readReader(new InputStreamReader(new URL(outputDir, "report.js").openStream(), "UTF-8")); assertJsFunctionCallSequence(asList("" + "formatter.uri(\"file:some/path/some.feature\");\n", - "formatter.feature({\n" + + "formatter.feature({\n" + " \"name\": \"\",\n" + " \"description\": \"\",\n" + " \"keyword\": \"Feature\"\n" + "});\n", - "formatter.scenario({\n" + + "formatter.scenario({\n" + " \"name\": \"some cukes\",\n" + " \"description\": \"\",\n" + " \"keyword\": \"Scenario\"\n" + "});\n", - "formatter.step({\n" + + "formatter.step({\n" + " \"name\": \"first step\",\n" + " \"keyword\": \"Given \"\n" + "});\n", - "formatter.match({\n" + + "formatter.match({\n" + " \"location\": \"path/step_definitions.java:3\"\n" + "});\n", - "formatter.result({\n" + + "formatter.result({\n" + " \"status\": \"passed\"\n" + "});\n", - "formatter.embedding(\"image/png\", \"embedded0.png\", \"Fake image\");\n", - "formatter.after({\n" + + "formatter.embedding(\"image/png\", \"embedded0.png\", \"Fake image\");\n", + "formatter.after({\n" + " \"status\": \"passed\"\n" + "});\n", - "formatter.embedding(\"text/plain\", \"dodgy stack trace here\", null);\n", - "formatter.after({\n" + + "formatter.embedding(\"text/plain\", \"dodgy stack trace here\", null);\n", + "formatter.after({\n" + " \"status\": \"passed\"\n" + "});\n"), - reportJs); + readReportJs()); } @Test public void includes_uri() throws Throwable { writeReport(); - String reportJs = FixJava.readReader(new InputStreamReader(new URL(outputDir, "report.js").openStream(), "UTF-8")); - assertContains("formatter.uri(\"file:some/path/some.feature\");", reportJs); + assertContains("formatter.uri(\"file:some/path/some.feature\");", readReportJs()); } @Test public void included_embedding() throws Throwable { writeReport(); - String reportJs = FixJava.readReader(new InputStreamReader(new URL(outputDir, "report.js").openStream(), "UTF-8")); + String reportJs = readReportJs(); assertContains("formatter.embedding(\"image/png\", \"embedded0.png\", \"Fake image\");", reportJs); assertContains("formatter.embedding(\"text/plain\", \"dodgy stack trace here\", null);", reportJs); } @Test - public void should_handle_a_single_scenario() throws Throwable { + public void should_handle_a_single_scenario() { CucumberFeature feature = feature("path/test.feature", "" + - "Feature: feature name\n" + - " Scenario: scenario name\n" + - " Given first step\n" + - " Then second step\n"); + "Feature: feature name\n" + + " Scenario: scenario name\n" + + " Given first step\n" + + " Then second step\n"); features.add(feature); stepsToResult.put("first step", result("passed")); stepsToResult.put("second step", result("passed")); stepsToLocation.put("first step", "path/step_definitions.java:3"); stepsToLocation.put("second step", "path/step_definitions.java:7"); - stepDuration = 1L; + stepDuration = ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -162,19 +166,19 @@ public void should_handle_a_single_scenario() throws Throwable { "formatter.result({\n" + " \"status\": \"passed\"\n" + "});"), - formatterOutput); + formatterOutput); } @Test - public void should_handle_backgound() throws Throwable { + public void should_handle_backgound() { CucumberFeature feature = feature("path/test.feature", "" + - "Feature: feature name\n" + - " Background: background name\n" + - " Given first step\n" + - " Scenario: scenario 1\n" + - " Then second step\n" + - " Scenario: scenario 2\n" + - " Then third step\n"); + "Feature: feature name\n" + + " Background: background name\n" + + " Given first step\n" + + " Scenario: scenario 1\n" + + " Then second step\n" + + " Scenario: scenario 2\n" + + " Then third step\n"); features.add(feature); stepsToResult.put("first step", result("passed")); stepsToResult.put("second step", result("passed")); @@ -182,7 +186,7 @@ public void should_handle_backgound() throws Throwable { stepsToLocation.put("first step", "path/step_definitions.java:3"); stepsToLocation.put("second step", "path/step_definitions.java:7"); stepsToLocation.put("third step", "path/step_definitions.java:11"); - stepDuration = 1L; + stepDuration = ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -247,20 +251,20 @@ public void should_handle_backgound() throws Throwable { "formatter.result({\n" + " \"status\": \"passed\"\n" + "});\n"), - formatterOutput); + formatterOutput); } @Test - public void should_handle_scenario_outline() throws Throwable { + public void should_handle_scenario_outline() { CucumberFeature feature = feature("path/test.feature", "" + - "Feature: feature name\n" + - " Scenario Outline: outline name\n" + - " Given first step\n" + - " Then step\n" + - " Examples: examples name\n" + - " | arg |\n" + - " | second |\n" + - " | third |\n"); + "Feature: feature name\n" + + " Scenario Outline: outline name\n" + + " Given first step\n" + + " Then step\n" + + " Examples: examples name\n" + + " | arg |\n" + + " | second |\n" + + " | third |\n"); features.add(feature); stepsToResult.put("first step", result("passed")); stepsToResult.put("second step", result("passed")); @@ -268,7 +272,7 @@ public void should_handle_scenario_outline() throws Throwable { stepsToLocation.put("first step", "path/step_definitions.java:3"); stepsToLocation.put("second step", "path/step_definitions.java:7"); stepsToLocation.put("third step", "path/step_definitions.java:11"); - stepDuration = 1L; + stepDuration = ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -364,20 +368,20 @@ public void should_handle_scenario_outline() throws Throwable { "formatter.result({\n" + " \"status\": \"passed\"\n" + "});"), - formatterOutput); + formatterOutput); } @Test - public void should_handle_before_hooks() throws Throwable { + public void should_handle_before_hooks() { CucumberFeature feature = feature("path/test.feature", "" + - "Feature: feature name\n" + - " Scenario: scenario name\n" + - " Given first step\n"); + "Feature: feature name\n" + + " Scenario: scenario name\n" + + " Given first step\n"); features.add(feature); stepsToResult.put("first step", result("passed")); stepsToLocation.put("first step", "path/step_definitions.java:3"); hooks.add(TestHelper.hookEntry("before", result("passed"))); - stepDuration = 1L; + stepDuration = ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -400,20 +404,20 @@ public void should_handle_before_hooks() throws Throwable { "formatter.result({\n" + " \"status\": \"passed\"\n" + "});\n"), - formatterOutput); + formatterOutput); } @Test - public void should_handle_after_hooks() throws Throwable { + public void should_handle_after_hooks() { CucumberFeature feature = feature("path/test.feature", "" + - "Feature: feature name\n" + - " Scenario: scenario name\n" + - " Given first step\n"); + "Feature: feature name\n" + + " Scenario: scenario name\n" + + " Given first step\n"); features.add(feature); stepsToResult.put("first step", result("passed")); stepsToLocation.put("first step", "path/step_definitions.java:3"); hooks.add(TestHelper.hookEntry("after", result("passed"))); - stepDuration = 1L; + stepDuration = ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -436,11 +440,11 @@ public void should_handle_after_hooks() throws Throwable { "formatter.after({\n" + " \"status\": \"passed\"\n" + "});\n"), - formatterOutput); + formatterOutput); } @Test - public void should_handle_after_step_hooks() throws Throwable { + public void should_handle_after_step_hooks() { CucumberFeature feature = feature("path/test.feature", "" + "Feature: feature name\n" + " Scenario: scenario name\n" + @@ -453,7 +457,7 @@ public void should_handle_after_step_hooks() throws Throwable { stepsToLocation.put("second step", "path/step_definitions.java:4"); hooks.add(TestHelper.hookEntry("afterstep", result("passed"))); hooks.add(TestHelper.hookEntry("afterstep", result("passed"))); - stepDuration = 1L; + stepDuration = ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -499,17 +503,17 @@ public void should_handle_after_step_hooks() throws Throwable { } @Test - public void should_handle_output_from_before_hooks() throws Throwable { + public void should_handle_output_from_before_hooks() { CucumberFeature feature = feature("path/test.feature", "" + - "Feature: feature name\n" + - " Scenario: scenario name\n" + - " Given first step\n"); + "Feature: feature name\n" + + " Scenario: scenario name\n" + + " Given first step\n"); features.add(feature); stepsToResult.put("first step", result("passed")); stepsToLocation.put("first step", "path/step_definitions.java:3"); hooks.add(TestHelper.hookEntry("before", result("passed"))); hookActions.add(createWriteHookAction("printed from hook")); - stepDuration = 1L; + stepDuration = ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -533,21 +537,21 @@ public void should_handle_output_from_before_hooks() throws Throwable { "formatter.result({\n" + " \"status\": \"passed\"\n" + "});\n"), - formatterOutput); + formatterOutput); } @Test - public void should_handle_output_from_after_hooks() throws Throwable { + public void should_handle_output_from_after_hooks() { CucumberFeature feature = feature("path/test.feature", "" + - "Feature: feature name\n" + - " Scenario: scenario name\n" + - " Given first step\n"); + "Feature: feature name\n" + + " Scenario: scenario name\n" + + " Given first step\n"); features.add(feature); stepsToResult.put("first step", result("passed")); stepsToLocation.put("first step", "path/step_definitions.java:3"); hooks.add(TestHelper.hookEntry("after", result("passed"))); hookActions.add(createWriteHookAction("printed from hook")); - stepDuration = 1L; + stepDuration = ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -571,11 +575,11 @@ public void should_handle_output_from_after_hooks() throws Throwable { "formatter.after({\n" + " \"status\": \"passed\"\n" + "});\n"), - formatterOutput); + formatterOutput); } @Test - public void should_handle_output_from_after_step_hooks() throws Throwable { + public void should_handle_output_from_after_step_hooks() { CucumberFeature feature = feature("path/test.feature", "" + "Feature: feature name\n" + " Scenario: scenario name\n" + @@ -588,7 +592,7 @@ public void should_handle_output_from_after_step_hooks() throws Throwable { stepsToLocation.put("second step", "path/step_definitions.java:4"); hooks.add(TestHelper.hookEntry("afterstep", result("passed"))); hookActions.add(createWriteHookAction("printed from hook")); - stepDuration = 1L; + stepDuration = ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -630,17 +634,17 @@ public void should_handle_output_from_after_step_hooks() throws Throwable { } @Test - public void should_handle_text_embeddings_from_before_hooks() throws Throwable { + public void should_handle_text_embeddings_from_before_hooks() { CucumberFeature feature = feature("path/test.feature", "" + - "Feature: feature name\n" + - " Scenario: scenario name\n" + - " Given first step\n"); + "Feature: feature name\n" + + " Scenario: scenario name\n" + + " Given first step\n"); features.add(feature); stepsToResult.put("first step", result("passed")); stepsToLocation.put("first step", "path/step_definitions.java:3"); hooks.add(TestHelper.hookEntry("before", result("passed"))); - hookActions.add(createEmbedHookAction("embedded from hook".getBytes("US-ASCII"), "text/ascii")); - stepDuration = 1L; + hookActions.add(createEmbedHookAction("embedded from hook".getBytes(US_ASCII), "text/ascii")); + stepDuration = ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -664,7 +668,14 @@ public void should_handle_text_embeddings_from_before_hooks() throws Throwable { "formatter.result({\n" + " \"status\": \"passed\"\n" + "});\n"), - formatterOutput); + formatterOutput); + } + + private String readReportJs() throws IOException { + InputStream reportJsStream = new URL(outputDir, "report.js").openStream(); + try (BufferedReader br = new BufferedReader(new InputStreamReader(reportJsStream, UTF_8))) { + return br.lines().collect(Collectors.joining(System.lineSeparator())); + } } private void assertJsFunctionCallSequence(List expectedList, String actual) { @@ -726,20 +737,20 @@ private void assertContains(String substring, String string) { } } - private void runFeaturesWithFormatter(URL outputDir) throws Throwable { + private void runFeaturesWithFormatter(URL outputDir) { final HTMLFormatter f = new HTMLFormatter(outputDir); CucumberFeature feature = feature("some/path/some.feature", "" + - "Feature:\n" + - " Scenario: some cukes\n" + - " Given first step\n"); + "Feature:\n" + + " Scenario: some cukes\n" + + " Given first step\n"); features.add(feature); stepsToResult.put("first step", result("passed")); stepsToLocation.put("first step", "path/step_definitions.java:3"); hooks.add(TestHelper.hookEntry("after", result("passed"))); hooks.add(TestHelper.hookEntry("after", result("passed"))); - hookActions.add(createEmbedHookAction("fakedata".getBytes("US-ASCII"), "image/png", "Fake image")); - hookActions.add(createEmbedHookAction("dodgy stack trace here".getBytes("US-ASCII"), "text/plain")); - stepDuration = 1L; + hookActions.add(createEmbedHookAction("fakedata".getBytes(US_ASCII), "image/png", "Fake image")); + hookActions.add(createEmbedHookAction("dodgy stack trace here".getBytes(US_ASCII), "text/plain")); + stepDuration = ofMillis(1L); runFeaturesWithFormatter(f); } diff --git a/core/src/test/java/cucumber/runtime/formatter/JSONFormatterTest.java b/core/src/test/java/io/cucumber/core/plugin/JSONFormatterTest.java similarity index 95% rename from core/src/test/java/cucumber/runtime/formatter/JSONFormatterTest.java rename to core/src/test/java/io/cucumber/core/plugin/JSONFormatterTest.java index 7a05e72d5a..fd905b7192 100755 --- a/core/src/test/java/cucumber/runtime/formatter/JSONFormatterTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/JSONFormatterTest.java @@ -1,27 +1,19 @@ -package cucumber.runtime.formatter; - -import cucumber.api.Result; -import cucumber.runner.EventBus; -import cucumber.runner.TestBackendSupplier; -import cucumber.runner.TestHelper; -import cucumber.runner.TimeServiceEventBus; -import cucumber.runner.TimeServiceStub; -import io.cucumber.core.options.CommandlineOptionsParser; -import cucumber.runtime.HookDefinition; -import cucumber.runtime.Runtime; -import io.cucumber.core.options.RuntimeOptions; -import cucumber.runtime.io.ClasspathResourceLoader; -import cucumber.runtime.model.CucumberFeature; -import cucumber.runtime.snippets.FunctionNameGenerator; -import gherkin.pickles.PickleStep; -import gherkin.pickles.PickleTag; -import org.junit.Test; -import org.mockito.ArgumentMatchers; -import org.mockito.stubbing.Answer; +package io.cucumber.core.plugin; + +import static io.cucumber.core.runner.TestHelper.createEmbedHookAction; +import static io.cucumber.core.runner.TestHelper.createWriteHookAction; +import static io.cucumber.core.runner.TestHelper.result; +import static java.time.Duration.ofMillis; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs; import java.io.File; import java.io.IOException; import java.net.URI; +import java.time.Duration; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; import java.util.HashMap; @@ -29,15 +21,25 @@ import java.util.Map; import java.util.Scanner; -import static cucumber.runner.TestHelper.createEmbedHookAction; -import static cucumber.runner.TestHelper.createWriteHookAction; -import static cucumber.runner.TestHelper.result; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs; +import io.cucumber.core.event.Result; +import io.cucumber.core.options.CommandlineOptionsParser; +import io.cucumber.core.options.RuntimeOptions; +import io.cucumber.core.runner.TestHelper; +import org.junit.Test; +import org.mockito.ArgumentMatchers; +import org.mockito.stubbing.Answer; + +import gherkin.pickles.PickleTag; +import io.cucumber.core.backend.Glue; +import io.cucumber.core.backend.HookDefinition; +import io.cucumber.core.eventbus.EventBus; +import io.cucumber.core.io.ResourceLoader; +import io.cucumber.core.io.TestClasspathResourceLoader; +import io.cucumber.core.feature.CucumberFeature; +import io.cucumber.core.runner.TestBackendSupplier; +import io.cucumber.core.runtime.TimeServiceEventBus; +import io.cucumber.core.runner.ClockStub; +import io.cucumber.core.runtime.Runtime; public class JSONFormatterTest { @@ -47,11 +49,11 @@ public class JSONFormatterTest { private final List> hooks = new ArrayList<>(); private final List hookLocations = new ArrayList<>(); private final List> hookActions = new ArrayList<>(); - private Long stepDurationMillis = 0L; + private Duration stepDuration = Duration.ZERO; @Test public void featureWithOutlineTest() { - String actual = runFeaturesWithFormatter(asList("classpath:cucumber/runtime/formatter/JSONPrettyFormatterTest.feature")); + String actual = runFeaturesWithFormatter(asList("classpath:io/cucumber/core/plugin/JSONPrettyFormatterTest.feature")); String expected = new Scanner(getClass().getResourceAsStream("JSONPrettyFormatterTest.json"), "UTF-8").useDelimiter("\\A").next(); assertThat(actual, sameJSONAs(expected)); @@ -60,7 +62,7 @@ public void featureWithOutlineTest() { @Test public void featureWithOutlineTestParallel() throws Exception { - String actual = runFeaturesWithFormatterInParallel(asList("classpath:cucumber/runtime/formatter/JSONPrettyFormatterTest.feature")); + String actual = runFeaturesWithFormatterInParallel(asList("classpath:io/cucumber/core/plugin/JSONPrettyFormatterTest.feature")); String expected = new Scanner(getClass().getResourceAsStream("JSONPrettyFormatterTest.json"), "UTF-8").useDelimiter("\\A").next(); assertThat(actual, sameJSONAs(expected)); @@ -125,7 +127,7 @@ public void should_format_scenario_with_a_passed_step() { features.add(feature); stepsToResult.put("there are bananas", result("passed")); stepsToLocation.put("there are bananas", "StepDefs.there_are_bananas()"); - stepDurationMillis = 1L; + stepDuration = ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -179,7 +181,7 @@ public void should_format_scenario_with_a_failed_step() { features.add(feature); stepsToResult.put("there are bananas", result("failed")); stepsToLocation.put("there are bananas", "StepDefs.there_are_bananas()"); - stepDurationMillis = 1L; + stepDuration = ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -237,7 +239,7 @@ public void should_format_scenario_outline_with_one_example() { features.add(feature); stepsToResult.put("there are bananas", result("passed")); stepsToLocation.put("there are bananas", "StepDefs.there_are_bananas()"); - stepDurationMillis = 1L; + stepDuration = ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -301,7 +303,7 @@ public void should_format_feature_with_background() { stepsToLocation.put("there are bananas", "StepDefs.there_are_bananas()"); stepsToLocation.put("the monkey eats bananas", "StepDefs.monkey_eats_bananas()"); stepsToLocation.put("the monkey eats more bananas", "StepDefs.monkey_eats_more_bananas()"); - stepDurationMillis = 1L; + stepDuration = ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -421,7 +423,7 @@ public void should_format_feature_and_scenario_with_tags() { features.add(feature); stepsToResult.put("the monkey eats more bananas", result("passed")); stepsToLocation.put("the monkey eats more bananas", "StepDefs.monkey_eats_more_bananas()"); - stepDurationMillis = 1L; + stepDuration = ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -507,7 +509,7 @@ public void should_format_scenario_with_hooks() { hooks.add(TestHelper.hookEntry("after", result("passed"))); hookLocations.add("Hooks.before_hook_1()"); hookLocations.add("Hooks.after_hook_1()"); - stepDurationMillis = 1L; + stepDuration = ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -592,7 +594,7 @@ public void should_add_step_hooks_to_step() { hookLocations.add("Hooks.beforestep_hooks_1()"); hookLocations.add("Hooks.afterstep_hooks_1()"); hookLocations.add("Hooks.afterstep_hooks_2()"); - stepDurationMillis = 1L; + stepDuration = ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -723,7 +725,7 @@ public void should_handle_write_from_a_hook() { hooks.add(TestHelper.hookEntry("before", result("passed"))); hookLocations.add("Hooks.before_hook_1()"); hookActions.add(createWriteHookAction("printed from hook")); - stepDurationMillis = 1L; + stepDuration = ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -794,7 +796,7 @@ public void should_handle_embed_from_a_hook() { hooks.add(TestHelper.hookEntry("before", result("passed"))); hookLocations.add("Hooks.before_hook_1()"); hookActions.add(createEmbedHookAction(new byte[]{1, 2, 3}, "mime-type;base64")); - stepDurationMillis = 1L; + stepDuration = ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -868,7 +870,7 @@ public void should_handle_embed_with_name_from_a_hook() { hooks.add(TestHelper.hookEntry("before", result("passed"))); hookLocations.add("Hooks.before_hook_1()"); hookActions.add(createEmbedHookAction(new byte[]{1, 2, 3}, "mime-type;base64", "someEmbedding")); - stepDurationMillis = 1L; + stepDuration = ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -943,7 +945,7 @@ public void should_format_scenario_with_a_step_with_a_doc_string() { features.add(feature); stepsToResult.put("there are bananas", result("passed")); stepsToLocation.put("there are bananas", "StepDefs.there_are_bananas()"); - stepDurationMillis = 1L; + stepDuration = ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -1004,7 +1006,7 @@ public void should_format_scenario_with_a_step_with_a_doc_string_and_content_typ features.add(feature); stepsToResult.put("there are bananas", result("passed")); stepsToLocation.put("there are bananas", "StepDefs.there_are_bananas()"); - stepDurationMillis = 1L; + stepDuration = ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -1065,7 +1067,7 @@ public void should_format_scenario_with_a_step_with_a_data_table() { features.add(feature); stepsToResult.put("there are bananas", result("passed")); stepsToLocation.put("there are bananas", "StepDefs.there_are_bananas()"); - stepDurationMillis = 1L; + stepDuration = ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -1141,7 +1143,7 @@ public void should_handle_several_features() { stepsToResult.put("there are oranges", result("passed")); stepsToLocation.put("there are bananas", "StepDefs.there_are_bananas()"); stepsToLocation.put("there are oranges", "StepDefs.there_are_oranges()"); - stepDurationMillis = 1L; + stepDuration = ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -1225,7 +1227,7 @@ private String runFeaturesWithFormatterInParallel(final List featurePath File report = File.createTempFile("cucumber-jvm-junit", ".json"); final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - final ClasspathResourceLoader resourceLoader = new ClasspathResourceLoader(classLoader); + final ResourceLoader resourceLoader = TestClasspathResourceLoader.create(classLoader); List args = new ArrayList(); args.add("--threads"); @@ -1236,17 +1238,12 @@ private String runFeaturesWithFormatterInParallel(final List featurePath final TestBackendSupplier backendSupplier = new TestBackendSupplier() { @Override - public void loadGlue(cucumber.runtime.Glue glue, List gluePaths) { + public void loadGlue(Glue glue, List gluePaths) { glue.addBeforeHook(hook); } - - @Override - public List getSnippet(PickleStep step, String keyword, FunctionNameGenerator functionNameGenerator) { - return singletonList("TEST SNIPPET"); - } }; - final EventBus bus = new TimeServiceEventBus(new TimeServiceStub(1234)); + final EventBus bus = new TimeServiceEventBus(new ClockStub(ofMillis(1234L))); Appendable stringBuilder = new StringBuilder(); @@ -1274,21 +1271,16 @@ private String runFeaturesWithFormatter(final List featurePaths) { when(hook.matches(ArgumentMatchers.anyList())).thenReturn(true); final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - final ClasspathResourceLoader resourceLoader = new ClasspathResourceLoader(classLoader); + final ResourceLoader resourceLoader = TestClasspathResourceLoader.create(classLoader); final TestBackendSupplier backendSupplier = new TestBackendSupplier() { @Override - public void loadGlue(cucumber.runtime.Glue glue, List gluePaths) { + public void loadGlue(Glue glue, List gluePaths) { glue.addBeforeHook(hook); } - - @Override - public List getSnippet(PickleStep step, String keyword, FunctionNameGenerator functionNameGenerator) { - return singletonList("TEST SNIPPET"); - } }; - final EventBus bus = new TimeServiceEventBus(new TimeServiceStub(1234)); + final EventBus bus = new TimeServiceEventBus(new ClockStub(ofMillis(1234L))); Appendable stringBuilder = new StringBuilder(); @@ -1320,7 +1312,7 @@ private String runFeaturesWithFormatter() { .withHooks(hooks) .withHookLocations(hookLocations) .withHookActions(hookActions) - .withTimeServiceIncrement(stepDurationMillis) + .withTimeServiceIncrement(stepDuration) .build() .run(); diff --git a/core/src/test/java/cucumber/runtime/formatter/JUnitFormatterTest.java b/core/src/test/java/io/cucumber/core/plugin/JUnitFormatterTest.java similarity index 88% rename from core/src/test/java/cucumber/runtime/formatter/JUnitFormatterTest.java rename to core/src/test/java/io/cucumber/core/plugin/JUnitFormatterTest.java index 0826c22777..7773a9e71d 100644 --- a/core/src/test/java/cucumber/runtime/formatter/JUnitFormatterTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/JUnitFormatterTest.java @@ -1,15 +1,9 @@ -package cucumber.runtime.formatter; +package io.cucumber.core.plugin; -import cucumber.api.Result; -import cucumber.runner.TestHelper; -import cucumber.runtime.Utils; -import cucumber.runtime.model.CucumberFeature; -import org.custommonkey.xmlunit.Diff; -import org.custommonkey.xmlunit.XMLUnit; -import org.junit.AssumptionViolatedException; -import org.junit.Test; -import org.mockito.stubbing.Answer; -import org.xml.sax.SAXException; +import static io.cucumber.core.runner.TestHelper.result; +import static java.time.Duration.ZERO; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertTrue; import java.io.File; import java.io.FileInputStream; @@ -18,6 +12,7 @@ import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.StringWriter; +import java.time.Duration; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; import java.util.HashMap; @@ -25,9 +20,16 @@ import java.util.Map; import java.util.Scanner; -import static cucumber.runner.TestHelper.result; -import static java.util.Arrays.asList; -import static org.junit.Assert.assertTrue; +import io.cucumber.core.event.Result; +import org.custommonkey.xmlunit.Diff; +import org.custommonkey.xmlunit.XMLUnit; +import org.junit.AssumptionViolatedException; +import org.junit.Test; +import org.mockito.stubbing.Answer; +import org.xml.sax.SAXException; + +import io.cucumber.core.feature.CucumberFeature; +import io.cucumber.core.runner.TestHelper; public class JUnitFormatterTest { @@ -37,31 +39,31 @@ public class JUnitFormatterTest { private final List> hooks = new ArrayList<>(); private final List hookLocations = new ArrayList<>(); private final List> hookActions = new ArrayList<>(); - private Long stepDurationMillis = null; + private Duration stepDuration = null; @Test public void featureSimpleTest() throws Exception { - File report = runFeaturesWithJunitFormatter(asList("classpath:cucumber/runtime/formatter/JUnitFormatterTest_1.feature")); - assertXmlEqual("cucumber/runtime/formatter/JUnitFormatterTest_1.report.xml", report); + File report = runFeaturesWithJunitFormatter(asList("classpath:io/cucumber/core/plugin//JUnitFormatterTest_1.feature")); + assertXmlEqual("io/cucumber/core/plugin//JUnitFormatterTest_1.report.xml", report); } @Test public void featureWithBackgroundTest() throws Exception { - File report = runFeaturesWithJunitFormatter(asList("classpath:cucumber/runtime/formatter/JUnitFormatterTest_2.feature")); - assertXmlEqual("cucumber/runtime/formatter/JUnitFormatterTest_2.report.xml", report); + File report = runFeaturesWithJunitFormatter(asList("classpath:io/cucumber/core/plugin//JUnitFormatterTest_2.feature")); + assertXmlEqual("io/cucumber/core/plugin//JUnitFormatterTest_2.report.xml", report); } @Test public void featureWithOutlineTest() throws Exception { - File report = runFeaturesWithJunitFormatter(asList("classpath:cucumber/runtime/formatter/JUnitFormatterTest_3.feature")); - assertXmlEqual("cucumber/runtime/formatter/JUnitFormatterTest_3.report.xml", report); + File report = runFeaturesWithJunitFormatter(asList("classpath:io/cucumber/core/plugin//JUnitFormatterTest_3.feature")); + assertXmlEqual("io/cucumber/core/plugin//JUnitFormatterTest_3.report.xml", report); } @Test public void featureSimpleStrictTest() throws Exception { boolean strict = true; - File report = runFeaturesWithJunitFormatter(asList("classpath:cucumber/runtime/formatter/JUnitFormatterTest_1.feature"), strict); - assertXmlEqual("cucumber/runtime/formatter/JUnitFormatterTest_1_strict.report.xml", report); + File report = runFeaturesWithJunitFormatter(asList("classpath:io/cucumber/core/plugin//JUnitFormatterTest_1.feature"), strict); + assertXmlEqual("io/cucumber/core/plugin//JUnitFormatterTest_1_strict.report.xml", report); } @Test @@ -76,12 +78,12 @@ public void should_format_passed_scenario() throws Throwable { stepsToResult.put("first step", result("passed")); stepsToResult.put("second step", result("passed")); stepsToResult.put("third step", result("passed")); - stepDurationMillis = 1L; + stepDuration = Duration.ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); String expected = "\n" + - "\n" + + "\n" + " \n" + " \n" + - "\n" + + "\n" + " \n" + " \n" + - "\n" + + "\n" + " \n" + " \n" + - "\n" + + "\n" + " \n" + " \n" + - "\n" + + "\n" + " \n" + " \n" + - "\n" + + "\n" + " \n" + " \n" + - "\n" + + "\n" + " \n" + " \n" + - "\n" + + "\n" + " \n" + " \n" + - "\n" + + "\n" + " \n" + " \n" + - "\n" + + "\n" + " \n" + " \n" + - "\n" + + "\n" + " \n" + " \n" + - "\n" + + "\n" + " \n" + " featurePaths, bool .withHookLocations(hookLocations) .withHookActions(hookActions) .withTimeServiceType(TestHelper.TimeServiceType.FIXED_INCREMENT) - .withTimeServiceIncrement(0L) + .withTimeServiceIncrement(ZERO) .build() .run(); @@ -532,7 +534,7 @@ private String runFeaturesWithFormatter() throws IOException { .withHooks(hooks) .withHookLocations(hookLocations) .withHookActions(hookActions) - .withTimeServiceIncrement(stepDurationMillis) + .withTimeServiceIncrement(stepDuration) .build() .run(); @@ -556,7 +558,7 @@ private void assertXmlEqual(String expected, String actual) throws SAXException, } private JUnitFormatter createJUnitFormatter(final File report) throws IOException { - return new JUnitFormatter(Utils.toURL(report.getAbsolutePath())); + return new JUnitFormatter(report.toURI().toURL()); } private String getStackTrace(Throwable exception) { diff --git a/core/src/test/java/cucumber/runtime/formatter/JsonParallelRuntimeTest.java b/core/src/test/java/io/cucumber/core/plugin/JsonParallelRuntimeTest.java similarity index 56% rename from core/src/test/java/cucumber/runtime/formatter/JsonParallelRuntimeTest.java rename to core/src/test/java/io/cucumber/core/plugin/JsonParallelRuntimeTest.java index 49d041996e..ad09bc7ac7 100644 --- a/core/src/test/java/cucumber/runtime/formatter/JsonParallelRuntimeTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/JsonParallelRuntimeTest.java @@ -1,12 +1,12 @@ -package cucumber.runtime.formatter; +package io.cucumber.core.plugin; -import cucumber.runner.TimeServiceEventBus; -import cucumber.runner.TimeServiceStub; import io.cucumber.core.options.CommandlineOptionsParser; -import cucumber.runtime.Runtime; -import io.cucumber.core.options.RuntimeOptions; +import io.cucumber.core.runner.ClockStub; +import io.cucumber.core.runtime.TimeServiceEventBus; +import io.cucumber.core.runtime.Runtime; import org.junit.Test; +import static java.time.Duration.ZERO; import static org.junit.Assert.assertThat; import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs; @@ -21,26 +21,28 @@ public void testSingleFeature() { Runtime.builder() .withRuntimeOptions( new CommandlineOptionsParser() - .parse("src/test/resources/cucumber/runtime/formatter/JSONPrettyFormatterTest.feature") + .parse( + "--threads", "3", + "src/test/resources/io/cucumber/core/plugin/JSONPrettyFormatterTest.feature") .build() ) .withAdditionalPlugins(new JSONFormatter(parallel)) - .withEventBus(new TimeServiceEventBus(new TimeServiceStub(0))) + .withEventBus(new TimeServiceEventBus(new ClockStub(ZERO))) .build() .run(); StringBuilder serial = new StringBuilder(); - RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); - Runtime.builder() .withRuntimeOptions( new CommandlineOptionsParser() - .parse("src/test/resources/cucumber/runtime/formatter/JSONPrettyFormatterTest.feature") - .build(runtimeOptions) + .parse( + "--threads", "1", + "src/test/resources/io/cucumber/core/plugin/JSONPrettyFormatterTest.feature") + .build() ) .withAdditionalPlugins(new JSONFormatter(serial)) - .withEventBus(new TimeServiceEventBus(new TimeServiceStub(0))) + .withEventBus(new TimeServiceEventBus(new ClockStub(ZERO))) .build() .run(); @@ -54,11 +56,13 @@ public void testMultipleFeatures() { Runtime.builder() .withRuntimeOptions( new CommandlineOptionsParser() - .parse("src/test/resources/cucumber/runtime/formatter/FormatterInParallel.feature") + .parse("--threads", "3", + "src/test/resources/io/cucumber/core/plugin/JSONPrettyFormatterTest.feature", + "src/test/resources/io/cucumber/core/plugin/FormatterInParallel.feature") .build() ) .withAdditionalPlugins(new JSONFormatter(parallel)) - .withEventBus(new TimeServiceEventBus(new TimeServiceStub(0))) + .withEventBus(new TimeServiceEventBus(new ClockStub(ZERO))) .build() .run(); @@ -67,10 +71,12 @@ public void testMultipleFeatures() { Runtime.builder() .withRuntimeOptions(new CommandlineOptionsParser() - .parse("src/test/resources/cucumber/runtime/formatter/FormatterInParallel.feature") + .parse("--threads", "1", + "src/test/resources/io/cucumber/core/plugin/JSONPrettyFormatterTest.feature", + "src/test/resources/io/cucumber/core/plugin/FormatterInParallel.feature") .build()) .withAdditionalPlugins(new JSONFormatter(serial)) - .withEventBus(new TimeServiceEventBus(new TimeServiceStub(0))) + .withEventBus(new TimeServiceEventBus(new ClockStub(ZERO))) .build() .run(); diff --git a/core/src/test/java/cucumber/runtime/formatter/PickleStepMatcher.java b/core/src/test/java/io/cucumber/core/plugin/PickleStepMatcher.java similarity index 92% rename from core/src/test/java/cucumber/runtime/formatter/PickleStepMatcher.java rename to core/src/test/java/io/cucumber/core/plugin/PickleStepMatcher.java index 68eff11087..24860319a5 100755 --- a/core/src/test/java/cucumber/runtime/formatter/PickleStepMatcher.java +++ b/core/src/test/java/io/cucumber/core/plugin/PickleStepMatcher.java @@ -1,4 +1,4 @@ -package cucumber.runtime.formatter; +package io.cucumber.core.plugin; import gherkin.pickles.PickleStep; diff --git a/core/src/test/java/cucumber/runtime/formatter/PluginFactoryTest.java b/core/src/test/java/io/cucumber/core/plugin/PluginFactoryTest.java similarity index 62% rename from core/src/test/java/cucumber/runtime/formatter/PluginFactoryTest.java rename to core/src/test/java/io/cucumber/core/plugin/PluginFactoryTest.java index 073707abae..1ebd2d2caf 100644 --- a/core/src/test/java/cucumber/runtime/formatter/PluginFactoryTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/PluginFactoryTest.java @@ -1,14 +1,14 @@ -package cucumber.runtime.formatter; - -import cucumber.api.PickleStepTestStep; -import cucumber.api.Result; -import cucumber.api.TestCase; -import cucumber.api.event.TestStepFinished; -import cucumber.runner.EventBus; -import cucumber.runner.TimeServiceEventBus; -import cucumber.runner.TimeServiceStub; -import cucumber.runtime.CucumberException; -import cucumber.runtime.Utils; +package io.cucumber.core.plugin; + +import io.cucumber.core.event.PickleStepTestStep; +import io.cucumber.core.event.Result; +import io.cucumber.core.event.Status; +import io.cucumber.core.event.TestCase; +import io.cucumber.core.event.TestStepFinished; +import io.cucumber.core.eventbus.EventBus; +import io.cucumber.core.exception.CucumberException; +import io.cucumber.core.runtime.TimeServiceEventBus; +import io.cucumber.core.runner.ClockStub; import org.junit.Test; import java.io.ByteArrayOutputStream; @@ -16,10 +16,13 @@ import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; +import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import static io.cucumber.core.options.TestPluginOption.parse; +import static java.time.Duration.ZERO; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; @@ -33,20 +36,20 @@ public class PluginFactoryTest { @Test public void instantiates_junit_plugin_with_file_arg() throws IOException { - Object plugin = fc.create("junit:" + File.createTempFile("cucumber", "xml")); + Object plugin = fc.create(parse("junit:" + File.createTempFile("cucumber", "xml"))); assertEquals(JUnitFormatter.class, plugin.getClass()); } @Test public void instantiates_html_plugin_with_dir_arg() throws IOException { - Object plugin = fc.create("html:" + TempDir.createTempDirectory().getAbsolutePath()); + Object plugin = fc.create(parse("html:" + TempDir.createTempDirectory().getAbsolutePath())); assertEquals(HTMLFormatter.class, plugin.getClass()); } @Test public void fails_to_instantiate_html_plugin_without_dir_arg() throws IOException { try { - fc.create("html"); + fc.create(parse("html")); fail(); } catch (CucumberException e) { assertEquals("You must supply an output argument to html. Like so: html:output", e.getMessage()); @@ -55,25 +58,25 @@ public void fails_to_instantiate_html_plugin_without_dir_arg() throws IOExceptio @Test public void instantiates_pretty_plugin_with_file_arg() throws IOException { - Object plugin = fc.create("pretty:" + Utils.toURL(TempDir.createTempFile().getAbsolutePath())); + Object plugin = fc.create(parse("pretty:" + TempDir.createTempFile().toURI().toURL())); assertEquals(PrettyFormatter.class, plugin.getClass()); } @Test public void instantiates_pretty_plugin_without_file_arg() { - Object plugin = fc.create("pretty"); + Object plugin = fc.create(parse("pretty")); assertEquals(PrettyFormatter.class, plugin.getClass()); } @Test public void instantiates_usage_plugin_without_file_arg() { - Object plugin = fc.create("usage"); + Object plugin = fc.create(parse("usage")); assertEquals(UsageFormatter.class, plugin.getClass()); } @Test public void instantiates_usage_plugin_with_file_arg() throws IOException { - Object plugin = fc.create("usage:" + TempDir.createTempFile().getAbsolutePath()); + Object plugin = fc.create(parse("usage:" + TempDir.createTempFile().getAbsolutePath())); assertEquals(UsageFormatter.class, plugin.getClass()); } @@ -88,11 +91,11 @@ public void plugin_does_not_buffer_its_output() { // Need to create a new plugin factory here since we need it to pick up the new value of System.out fc = new PluginFactory(); - ProgressFormatter plugin = (ProgressFormatter) fc.create("progress"); - EventBus bus = new TimeServiceEventBus(new TimeServiceStub(0)); + ProgressFormatter plugin = (ProgressFormatter) fc.create(parse("progress")); + EventBus bus = new TimeServiceEventBus(new ClockStub(ZERO)); plugin.setEventPublisher(bus); - Result result = new Result(Result.Type.PASSED, 0L, null); - TestStepFinished event = new TestStepFinished(bus.getTime(), bus.getTimeMillis(), mock(TestCase.class), mock(PickleStepTestStep.class), result); + Result result = new Result(Status.PASSED, ZERO, null); + TestStepFinished event = new TestStepFinished(bus.getInstant(), mock(TestCase.class), mock(PickleStepTestStep.class), result); bus.send(event); assertThat(mockSystemOut.toString(), is(not(""))); @@ -103,75 +106,83 @@ public void plugin_does_not_buffer_its_output() { @Test public void instantiates_single_custom_appendable_plugin_with_stdout() { - WantsAppendable plugin = (WantsAppendable) fc.create("cucumber.runtime.formatter.PluginFactoryTest$WantsAppendable"); + WantsAppendable plugin = (WantsAppendable) fc.create(parse("io.cucumber.core.plugin.PluginFactoryTest$WantsAppendable")); assertThat(plugin.out, is(instanceOf(PrintStream.class))); try { - fc.create("cucumber.runtime.formatter.PluginFactoryTest$WantsAppendable"); + fc.create(parse("io.cucumber.core.plugin.PluginFactoryTest$WantsAppendable")); fail(); } catch (CucumberException expected) { - assertEquals("Only one formatter can use STDOUT, now both cucumber.runtime.formatter.PluginFactoryTest$WantsAppendable " + - "and cucumber.runtime.formatter.PluginFactoryTest$WantsAppendable use it. " + - "If you use more than one formatter you must specify output path with PLUGIN:PATH_OR_URL", expected.getMessage()); + assertEquals("Only one plugin can use STDOUT, now both io.cucumber.core.plugin.PluginFactoryTest$WantsAppendable " + + "and io.cucumber.core.plugin.PluginFactoryTest$WantsAppendable use it. " + + "If you use more than one plugin you must specify output path with PLUGIN:PATH_OR_URL", expected.getMessage()); } } @Test public void instantiates_custom_appendable_plugin_with_stdout_and_file() throws IOException { - WantsAppendable plugin = (WantsAppendable) fc.create("cucumber.runtime.formatter.PluginFactoryTest$WantsAppendable"); + WantsAppendable plugin = (WantsAppendable) fc.create(parse("io.cucumber.core.plugin.PluginFactoryTest$WantsAppendable")); assertThat(plugin.out, is(instanceOf(PrintStream.class))); - WantsAppendable plugin2 = (WantsAppendable) fc.create("cucumber.runtime.formatter.PluginFactoryTest$WantsAppendable:" + TempDir.createTempFile().getAbsolutePath()); + WantsAppendable plugin2 = (WantsAppendable) fc.create(parse("io.cucumber.core.plugin.PluginFactoryTest$WantsAppendable:" + TempDir.createTempFile().getAbsolutePath())); assertEquals(UTF8OutputStreamWriter.class, plugin2.out.getClass()); } @Test public void instantiates_custom_url_plugin() throws IOException { - WantsUrl plugin = (WantsUrl) fc.create("cucumber.runtime.formatter.PluginFactoryTest$WantsUrl:halp"); + WantsUrl plugin = (WantsUrl) fc.create(parse("io.cucumber.core.plugin.PluginFactoryTest$WantsUrl:halp")); assertEquals(new URL("file:halp/"), plugin.out); } @Test public void instantiates_custom_url_plugin_with_http() throws IOException { - WantsUrl plugin = (WantsUrl) fc.create("cucumber.runtime.formatter.PluginFactoryTest$WantsUrl:http://halp/"); + WantsUrl plugin = (WantsUrl) fc.create(parse("io.cucumber.core.plugin.PluginFactoryTest$WantsUrl:http://halp/")); assertEquals(new URL("http://halp/"), plugin.out); } @Test public void instantiates_custom_uri_plugin_with_ws() throws IOException, URISyntaxException { - WantsUri plugin = (WantsUri) fc.create("cucumber.runtime.formatter.PluginFactoryTest$WantsUri:ws://halp/"); + WantsUri plugin = (WantsUri) fc.create(parse("io.cucumber.core.plugin.PluginFactoryTest$WantsUri:ws://halp/")); assertEquals(new URI("ws://halp/"), plugin.out); } @Test public void instantiates_custom_file_plugin() throws IOException { - WantsFile plugin = (WantsFile) fc.create("cucumber.runtime.formatter.PluginFactoryTest$WantsFile:halp.txt"); + WantsFile plugin = (WantsFile) fc.create(parse("io.cucumber.core.plugin.PluginFactoryTest$WantsFile:halp.txt")); assertEquals(new File("halp.txt"), plugin.out); } @Test public void instantiates_custom_string_arg_plugin() throws IOException { - WantsString plugin = (WantsString) fc.create("cucumber.runtime.formatter.PluginFactoryTest$WantsString:hello"); + WantsString plugin = (WantsString) fc.create(parse("io.cucumber.core.plugin.PluginFactoryTest$WantsString:hello")); assertEquals("hello", plugin.arg); } @Test public void instantiates_plugin_using_empty_constructor_when_unspecified() throws IOException { - WantsStringOrDefault plugin = (WantsStringOrDefault) fc.create("cucumber.runtime.formatter.PluginFactoryTest$WantsStringOrDefault"); + WantsStringOrDefault plugin = (WantsStringOrDefault) fc.create(parse("io.cucumber.core.plugin.PluginFactoryTest$WantsStringOrDefault")); assertEquals("defaultValue", plugin.arg); } @Test public void instantiates_plugin_using_arg_constructor_when_specified() throws IOException { - WantsStringOrDefault plugin = (WantsStringOrDefault) fc.create("cucumber.runtime.formatter.PluginFactoryTest$WantsStringOrDefault:hello"); + WantsStringOrDefault plugin = (WantsStringOrDefault) fc.create(parse("io.cucumber.core.plugin.PluginFactoryTest$WantsStringOrDefault:hello")); assertEquals("hello", plugin.arg); } @Test public void instantiates_timeline_plugin_with_dir_arg() throws IOException { - Object plugin = fc.create("timeline:" + TempDir.createTempDirectory().getAbsolutePath()); + Object plugin = fc.create(parse("timeline:" + TempDir.createTempDirectory().getAbsolutePath())); assertEquals(TimelineFormatter.class, plugin.getClass()); } + + @Test + public void test_url() throws MalformedURLException { + URL dotCucumber = PluginFactory.toURL("foo/bar/.cucumber"); + URL url = new URL(dotCucumber, "stepdefs.json"); + assertEquals(new URL("file:foo/bar/.cucumber/stepdefs.json"), url); + } + public static class WantsAppendable extends StubFormatter { public final Appendable out; diff --git a/core/src/test/java/io/cucumber/core/plugin/PluginsTest.java b/core/src/test/java/io/cucumber/core/plugin/PluginsTest.java new file mode 100644 index 0000000000..9d2216db41 --- /dev/null +++ b/core/src/test/java/io/cucumber/core/plugin/PluginsTest.java @@ -0,0 +1,84 @@ +package io.cucumber.core.plugin; + +import io.cucumber.core.event.Event; +import io.cucumber.core.event.EventPublisher; +import io.cucumber.core.options.RuntimeOptions; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.quality.Strictness.STRICT_STUBS; + +public class PluginsTest { + + @Rule + public MockitoRule mockitoRule = MockitoJUnit.rule().strictness(STRICT_STUBS); + + @Mock + private EventPublisher rootEventPublisher; + + @Captor + private ArgumentCaptor eventPublisher; + + private PluginFactory pluginFactory = new PluginFactory(); + + @Test + public void shouldSetStrictOnPlugin() { + RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); + Plugins plugins = new Plugins(pluginFactory, runtimeOptions); + StrictAware plugin = mock(StrictAware.class); + plugins.addPlugin(plugin); + verify(plugin).setStrict(false); + } + + @Test + public void shouldSetMonochromeOnPlugin() { + RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); + Plugins plugins = new Plugins(pluginFactory, runtimeOptions); + ColorAware plugin = mock(ColorAware.class); + plugins.addPlugin(plugin); + verify(plugin).setMonochrome(false); + } + + @Test + public void shouldSetConcurrentEventListener() { + RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); + Plugins plugins = new Plugins(pluginFactory, runtimeOptions); + ConcurrentEventListener plugin = mock(ConcurrentEventListener.class); + plugins.addPlugin(plugin); + plugins.setEventBusOnEventListenerPlugins(rootEventPublisher); + verify(plugin, times(1)).setEventPublisher(rootEventPublisher); + } + + @Test + public void shouldSetNonConcurrentEventListener() { + RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); + Plugins plugins = new Plugins(pluginFactory, runtimeOptions); + EventListener plugin = mock(EventListener.class); + plugins.addPlugin(plugin); + plugins.setSerialEventBusOnEventListenerPlugins(rootEventPublisher); + verify(plugin, times(1)).setEventPublisher(eventPublisher.capture()); + assertEquals(CanonicalOrderEventPublisher.class, eventPublisher.getValue().getClass()); + } + + @Test + public void shouldRegisterCanonicalOrderEventPublisherWithRootEventPublisher() { + RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); + Plugins plugins = new Plugins(pluginFactory, runtimeOptions); + EventListener plugin = mock(EventListener.class); + plugins.addPlugin(plugin); + plugins.setSerialEventBusOnEventListenerPlugins(rootEventPublisher); + verify(rootEventPublisher, times(1)).registerHandlerFor(eq(Event.class), ArgumentMatchers.any()); + } + +} \ No newline at end of file diff --git a/core/src/test/java/cucumber/runtime/formatter/PrettyFormatterTest.java b/core/src/test/java/io/cucumber/core/plugin/PrettyFormatterTest.java similarity index 96% rename from core/src/test/java/cucumber/runtime/formatter/PrettyFormatterTest.java rename to core/src/test/java/io/cucumber/core/plugin/PrettyFormatterTest.java index eb9950ec04..068d40fe99 100755 --- a/core/src/test/java/cucumber/runtime/formatter/PrettyFormatterTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/PrettyFormatterTest.java @@ -1,13 +1,11 @@ -package cucumber.runtime.formatter; - -import cucumber.api.Result; -import io.cucumber.stepexpression.TypeRegistry; -import cucumber.api.formatter.AnsiEscapes; -import cucumber.runtime.DefinitionArgument; -import cucumber.runner.TestHelper; -import cucumber.runtime.model.CucumberFeature; -import io.cucumber.stepexpression.StepExpression; -import io.cucumber.stepexpression.StepExpressionFactory; +package io.cucumber.core.plugin; + +import io.cucumber.core.event.Result; +import io.cucumber.core.stepexpression.TypeRegistry; +import io.cucumber.core.runner.TestHelper; +import io.cucumber.core.feature.CucumberFeature; +import io.cucumber.core.stepexpression.StepExpression; +import io.cucumber.core.stepexpression.StepExpressionFactory; import org.junit.Test; import org.mockito.stubbing.Answer; @@ -18,9 +16,10 @@ import java.util.Locale; import java.util.Map; -import static cucumber.runner.TestHelper.createWriteHookAction; -import static cucumber.runner.TestHelper.feature; -import static cucumber.runner.TestHelper.result; +import static io.cucumber.core.runner.TestDefinitionArgument.createArguments; +import static io.cucumber.core.runner.TestHelper.createWriteHookAction; +import static io.cucumber.core.runner.TestHelper.feature; +import static io.cucumber.core.runner.TestHelper.result; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertThat; @@ -394,7 +393,7 @@ public void should_mark_subsequent_arguments_in_steps() throws Throwable { 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))); + String formattedText = prettyFormatter.formatStepText("Given ", stepText, formats.get("passed"), formats.get("passed_arg"), createArguments(expression.match(stepText))); assertThat(formattedText, equalTo(AnsiEscapes.GREEN + "Given " + AnsiEscapes.RESET + AnsiEscapes.GREEN + "text " + AnsiEscapes.RESET + @@ -414,7 +413,7 @@ public void should_mark_nested_argument_as_part_of_full_argument() { PrettyFormatter prettyFormatter = new PrettyFormatter(null); 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 ", stepText, formats.get("passed"), formats.get("passed_arg"), createArguments(expression.match(stepText))); assertThat(formattedText, equalTo(AnsiEscapes.GREEN + "Given " + AnsiEscapes.RESET + AnsiEscapes.GREEN + "the order is placed" + AnsiEscapes.RESET + @@ -429,7 +428,7 @@ public void should_mark_nested_arguments_as_part_of_enclosing_argument() { 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 ", stepText, formats.get("passed"), formats.get("passed_arg"), createArguments(expression.match(stepText))); assertThat(formattedText, equalTo(AnsiEscapes.GREEN + "Given " + AnsiEscapes.RESET + diff --git a/core/src/test/java/cucumber/runtime/formatter/RerunFormatterTest.java b/core/src/test/java/io/cucumber/core/plugin/RerunFormatterTest.java similarity index 96% rename from core/src/test/java/cucumber/runtime/formatter/RerunFormatterTest.java rename to core/src/test/java/io/cucumber/core/plugin/RerunFormatterTest.java index e6749f39fd..6a03a9ef52 100755 --- a/core/src/test/java/cucumber/runtime/formatter/RerunFormatterTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/RerunFormatterTest.java @@ -1,8 +1,8 @@ -package cucumber.runtime.formatter; +package io.cucumber.core.plugin; -import cucumber.api.Result; -import cucumber.runner.TestHelper; -import cucumber.runtime.model.CucumberFeature; +import io.cucumber.core.event.Result; +import io.cucumber.core.runner.TestHelper; +import io.cucumber.core.feature.CucumberFeature; import org.junit.Test; import java.util.AbstractMap.SimpleEntry; @@ -11,7 +11,8 @@ import java.util.List; import java.util.Map; -import static cucumber.runner.TestHelper.result; +import static io.cucumber.core.runner.TestHelper.result; +import static java.time.Duration.ZERO; import static org.junit.Assert.assertEquals; public class RerunFormatterTest { @@ -211,7 +212,7 @@ private String runFeaturesWithFormatter(boolean isStrict) { .withFeatures(features) .withStepsToResult(stepsToResult) .withHooks(hooks) - .withTimeServiceIncrement(0L) + .withTimeServiceIncrement(ZERO) .build() .run(); diff --git a/core/src/test/java/cucumber/runtime/formatter/StatsTest.java b/core/src/test/java/io/cucumber/core/plugin/StatsTest.java similarity index 69% rename from core/src/test/java/cucumber/runtime/formatter/StatsTest.java rename to core/src/test/java/io/cucumber/core/plugin/StatsTest.java index a94f1c2190..c3b2fe85c4 100755 --- a/core/src/test/java/cucumber/runtime/formatter/StatsTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/StatsTest.java @@ -1,24 +1,24 @@ -package cucumber.runtime.formatter; +package io.cucumber.core.plugin; -import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.time.Duration.ofHours; +import static java.time.Duration.ofMillis; +import static java.time.Duration.ofMinutes; +import static java.time.Duration.ofSeconds; import static org.junit.Assert.assertThat; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.endsWith; import static org.hamcrest.CoreMatchers.startsWith; -import cucumber.api.Result; -import cucumber.api.formatter.AnsiEscapes; - import java.io.ByteArrayOutputStream; import java.io.PrintStream; +import java.time.Instant; import java.util.Locale; +import io.cucumber.core.event.Status; import org.junit.Test; public class StatsTest { - private static final long ANY_TIME = 1234567890; - private static final long ONE_MILLI_SECOND = MILLISECONDS.toNanos(1); - private static final long ONE_HOUR = 60 * Stats.ONE_MINUTE; + private static final Instant ANY_TIME = Instant.ofEpochMilli(1234567890); @Test public void should_print_zero_scenarios_zero_steps_if_nothing_has_executed() { @@ -36,10 +36,10 @@ public void should_only_print_sub_counts_if_not_zero() { Stats counter = createMonochromeSummaryCounter(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); - counter.addStep(Result.Type.PASSED); - counter.addStep(Result.Type.PASSED); - counter.addStep(Result.Type.PASSED); - counter.addScenario(Result.Type.PASSED, "scenario designation"); + counter.addStep(Status.PASSED); + counter.addStep(Status.PASSED); + counter.addStep(Status.PASSED); + counter.addScenario(Status.PASSED, "scenario designation"); counter.printStats(new PrintStream(baos)); assertThat(baos.toString(), startsWith(String.format( @@ -52,12 +52,12 @@ public void should_print_sub_counts_in_order_failed_ambiguous_skipped_pending_un Stats counter = createMonochromeSummaryCounter(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); - addOneStepScenario(counter, Result.Type.PASSED); - addOneStepScenario(counter, Result.Type.FAILED); - addOneStepScenario(counter, Result.Type.AMBIGUOUS); - addOneStepScenario(counter, Result.Type.PENDING); - addOneStepScenario(counter, Result.Type.UNDEFINED); - addOneStepScenario(counter, Result.Type.SKIPPED); + addOneStepScenario(counter, Status.PASSED); + addOneStepScenario(counter, Status.FAILED); + addOneStepScenario(counter, Status.AMBIGUOUS); + addOneStepScenario(counter, Status.PENDING); + addOneStepScenario(counter, Status.UNDEFINED); + addOneStepScenario(counter, Status.SKIPPED); counter.printStats(new PrintStream(baos)); assertThat(baos.toString(), containsString(String.format("" + @@ -70,12 +70,12 @@ public void should_print_sub_counts_in_order_failed_ambiguous_skipped_undefined_ Stats counter = createColorSummaryCounter(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); - addOneStepScenario(counter, Result.Type.PASSED); - addOneStepScenario(counter, Result.Type.FAILED); - addOneStepScenario(counter, Result.Type.AMBIGUOUS); - addOneStepScenario(counter, Result.Type.PENDING); - addOneStepScenario(counter, Result.Type.UNDEFINED); - addOneStepScenario(counter, Result.Type.SKIPPED); + addOneStepScenario(counter, Status.PASSED); + addOneStepScenario(counter, Status.FAILED); + addOneStepScenario(counter, Status.AMBIGUOUS); + addOneStepScenario(counter, Status.PENDING); + addOneStepScenario(counter, Status.UNDEFINED); + addOneStepScenario(counter, Status.SKIPPED); counter.printStats(new PrintStream(baos)); String colorSubCounts = "" + @@ -107,7 +107,7 @@ public void should_report_the_difference_between_finish_time_and_start_time() { ByteArrayOutputStream baos = new ByteArrayOutputStream(); counter.setStartTime(ANY_TIME); - counter.setFinishTime(ANY_TIME + 4*ONE_MILLI_SECOND); + counter.setFinishTime(ANY_TIME.plus(ofMillis(4))); counter.printStats(new PrintStream(baos)); assertThat(baos.toString(), endsWith(String.format( @@ -120,7 +120,7 @@ public void should_print_minutes_seconds_and_milliseconds() { ByteArrayOutputStream baos = new ByteArrayOutputStream(); counter.setStartTime(ANY_TIME); - counter.setFinishTime(ANY_TIME + Stats.ONE_MINUTE + Stats.ONE_SECOND + ONE_MILLI_SECOND); + counter.setFinishTime(ANY_TIME.plus(ofMinutes(1)).plus(ofSeconds(1)).plus(ofMillis(1))); counter.printStats(new PrintStream(baos)); assertThat(baos.toString(), endsWith(String.format( @@ -133,7 +133,7 @@ public void should_print_minutes_instead_of_hours() { ByteArrayOutputStream baos = new ByteArrayOutputStream(); counter.setStartTime(ANY_TIME); - counter.setFinishTime(ANY_TIME + ONE_HOUR + Stats.ONE_MINUTE); + counter.setFinishTime(ANY_TIME.plus(ofHours(1)).plus(ofMinutes(1))); counter.printStats(new PrintStream(baos)); assertThat(baos.toString(), endsWith(String.format( @@ -147,7 +147,7 @@ public void should_use_locale_for_decimal_separator() { ByteArrayOutputStream baos = new ByteArrayOutputStream(); counter.setStartTime(ANY_TIME); - counter.setFinishTime(ANY_TIME + Stats.ONE_MINUTE + Stats.ONE_SECOND + ONE_MILLI_SECOND); + counter.setFinishTime(ANY_TIME.plus(ofMinutes(1)).plus(ofSeconds(1)).plus(ofMillis(1))); counter.printStats(new PrintStream(baos)); assertThat(baos.toString(), endsWith(String.format("1m1,001s%n"))); @@ -158,14 +158,14 @@ public void should_print_failed_ambiguous_scenarios() { Stats counter = createMonochromeSummaryCounter(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); - counter.addStep(Result.Type.FAILED); - counter.addScenario(Result.Type.FAILED, "path/file.feature:3 # Scenario: scenario_name"); - counter.addStep(Result.Type.AMBIGUOUS); - counter.addScenario(Result.Type.AMBIGUOUS, "path/file.feature:3 # Scenario: scenario_name"); - counter.addStep(Result.Type.UNDEFINED); - counter.addScenario(Result.Type.UNDEFINED, "path/file.feature:3 # Scenario: scenario_name"); - counter.addStep(Result.Type.PENDING); - counter.addScenario(Result.Type.PENDING, "path/file.feature:3 # Scenario: scenario_name"); + counter.addStep(Status.FAILED); + counter.addScenario(Status.FAILED, "path/file.feature:3 # Scenario: scenario_name"); + counter.addStep(Status.AMBIGUOUS); + counter.addScenario(Status.AMBIGUOUS, "path/file.feature:3 # Scenario: scenario_name"); + counter.addStep(Status.UNDEFINED); + counter.addScenario(Status.UNDEFINED, "path/file.feature:3 # Scenario: scenario_name"); + counter.addStep(Status.PENDING); + counter.addScenario(Status.PENDING, "path/file.feature:3 # Scenario: scenario_name"); counter.printStats(new PrintStream(baos)); assertThat(baos.toString(), startsWith(String.format("" + @@ -183,14 +183,14 @@ public void should_print_failed_ambiguous_pending_undefined_scenarios_if_strict( Stats counter = createMonochromeSummaryCounter(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); - counter.addStep(Result.Type.FAILED); - counter.addScenario(Result.Type.FAILED, "path/file.feature:3 # Scenario: scenario_name"); - counter.addStep(Result.Type.AMBIGUOUS); - counter.addScenario(Result.Type.AMBIGUOUS, "path/file.feature:3 # Scenario: scenario_name"); - counter.addStep(Result.Type.UNDEFINED); - counter.addScenario(Result.Type.UNDEFINED, "path/file.feature:3 # Scenario: scenario_name"); - counter.addStep(Result.Type.PENDING); - counter.addScenario(Result.Type.PENDING, "path/file.feature:3 # Scenario: scenario_name"); + counter.addStep(Status.FAILED); + counter.addScenario(Status.FAILED, "path/file.feature:3 # Scenario: scenario_name"); + counter.addStep(Status.AMBIGUOUS); + counter.addScenario(Status.AMBIGUOUS, "path/file.feature:3 # Scenario: scenario_name"); + counter.addStep(Status.UNDEFINED); + counter.addScenario(Status.UNDEFINED, "path/file.feature:3 # Scenario: scenario_name"); + counter.addStep(Status.PENDING); + counter.addScenario(Status.PENDING, "path/file.feature:3 # Scenario: scenario_name"); counter.setStrict(true); counter.printStats(new PrintStream(baos)); @@ -210,7 +210,7 @@ public void should_print_failed_ambiguous_pending_undefined_scenarios_if_strict( "4 Scenarios"))); } - private void addOneStepScenario(Stats counter, Result.Type status) { + private void addOneStepScenario(Stats counter, Status status) { counter.addStep(status); counter.addScenario(status, "scenario designation"); } diff --git a/core/src/test/java/cucumber/runtime/formatter/StubFormatter.java b/core/src/test/java/io/cucumber/core/plugin/StubFormatter.java similarity index 61% rename from core/src/test/java/cucumber/runtime/formatter/StubFormatter.java rename to core/src/test/java/io/cucumber/core/plugin/StubFormatter.java index 7f97866ccd..16f22999ae 100644 --- a/core/src/test/java/cucumber/runtime/formatter/StubFormatter.java +++ b/core/src/test/java/io/cucumber/core/plugin/StubFormatter.java @@ -1,7 +1,6 @@ -package cucumber.runtime.formatter; +package io.cucumber.core.plugin; -import cucumber.api.event.EventListener; -import cucumber.api.event.EventPublisher; +import io.cucumber.core.event.EventPublisher; public class StubFormatter implements EventListener { diff --git a/core/src/test/java/cucumber/runtime/formatter/TempDir.java b/core/src/test/java/io/cucumber/core/plugin/TempDir.java similarity index 74% rename from core/src/test/java/cucumber/runtime/formatter/TempDir.java rename to core/src/test/java/io/cucumber/core/plugin/TempDir.java index 5e08744d2c..e03b861261 100644 --- a/core/src/test/java/cucumber/runtime/formatter/TempDir.java +++ b/core/src/test/java/io/cucumber/core/plugin/TempDir.java @@ -1,13 +1,13 @@ -package cucumber.runtime.formatter; +package io.cucumber.core.plugin; import java.io.File; import java.io.IOException; -public class TempDir { +class TempDir { private TempDir() { } - public static File createTempDirectory() throws IOException { + static File createTempDirectory() throws IOException { File temp = createTempFile(); if (!(temp.delete())) { @@ -23,7 +23,7 @@ public static File createTempDirectory() throws IOException { return temp; } - public static File createTempFile() throws IOException { + static File createTempFile() throws IOException { return File.createTempFile("temp", Long.toString(System.nanoTime())); } } diff --git a/core/src/test/java/cucumber/runtime/formatter/TestNGFormatterTest.java b/core/src/test/java/io/cucumber/core/plugin/TestNGFormatterTest.java similarity index 85% rename from core/src/test/java/cucumber/runtime/formatter/TestNGFormatterTest.java rename to core/src/test/java/io/cucumber/core/plugin/TestNGFormatterTest.java index 8528e06723..acc80d6e1f 100644 --- a/core/src/test/java/cucumber/runtime/formatter/TestNGFormatterTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/TestNGFormatterTest.java @@ -1,8 +1,8 @@ -package cucumber.runtime.formatter; +package io.cucumber.core.plugin; -import cucumber.api.Result; -import cucumber.runner.TestHelper; -import cucumber.runtime.model.CucumberFeature; +import io.cucumber.core.event.Result; +import io.cucumber.core.runner.TestHelper; +import io.cucumber.core.feature.CucumberFeature; import org.custommonkey.xmlunit.Diff; import org.custommonkey.xmlunit.Difference; import org.custommonkey.xmlunit.XMLUnit; @@ -14,6 +14,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.PrintWriter; +import java.time.Duration; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -21,8 +22,9 @@ import java.util.Scanner; import java.util.AbstractMap.SimpleEntry; -import static cucumber.runner.TestHelper.result; -import static cucumber.runtime.Utils.toURL; +import static io.cucumber.core.runner.TestHelper.result; +import static java.time.Duration.ZERO; +import static java.time.Duration.ofMillis; import static org.junit.Assert.assertTrue; public final class TestNGFormatterTest { @@ -33,7 +35,7 @@ public final class TestNGFormatterTest { private final List> hooks = new ArrayList<>(); private final List hookLocations = new ArrayList<>(); private final List> hookActions = new ArrayList<>(); - private Long stepDurationMillis = null; + private Duration stepDuration = null; @Test public final void testScenarioWithUndefinedSteps() throws Throwable { @@ -44,13 +46,13 @@ public final void testScenarioWithUndefinedSteps() throws Throwable { " Then step\n"); features.add(feature); stepsToResult.put("step", result("undefined")); - stepDurationMillis = 0L; + stepDuration = ZERO; String actual = runFeaturesWithFormatter(false); assertXmlEqual("" + "" + "" + - " " + - " " + + " " + + " " + " " + " " + " " + @@ -68,13 +70,13 @@ public void testScenarioWithUndefinedStepsStrict() throws Throwable { " Then step\n"); features.add(feature); stepsToResult.put("step", result("undefined")); - stepDurationMillis = 0L; + stepDuration = ZERO; String actual = runFeaturesWithFormatter(true); assertXmlEqual("" + "" + "" + - " " + - " " + + " " + + " " + " " + " " + " " + @@ -100,13 +102,13 @@ public final void testScenarioWithPendingSteps() throws Throwable { features.add(feature); stepsToResult.put("step1", result("pending")); stepsToResult.put("step2", result("skipped")); - stepDurationMillis = 0L; + stepDuration = ZERO; String actual = runFeaturesWithFormatter(false); assertXmlEqual("" + "" + "" + - " " + - " " + + " " + + " " + " " + " " + " " + @@ -125,16 +127,16 @@ public void testScenarioWithFailedSteps() throws Throwable { features.add(feature); stepsToResult.put("step1", result("failed", new TestNGException("message", "stacktrace"))); stepsToResult.put("step2", result("skipped")); - stepDurationMillis = 0L; + stepDuration = ZERO; String actual = runFeaturesWithFormatter(false); assertXmlEqual("" + "" + "" + - " " + - " " + + " " + + " " + " " + " " + - " " + + " " + " " + @@ -156,13 +158,13 @@ public final void testScenarioWithPassedSteps() throws Throwable { " Then step\n"); features.add(feature); stepsToResult.put("step", result("passed")); - stepDurationMillis = 0L; + stepDuration = ZERO; String actual = runFeaturesWithFormatter(false); assertXmlEqual("" + "" + "" + - " " + - " " + + " " + + " " + " " + " " + " " + @@ -184,13 +186,13 @@ public void testScenarioWithBackground() throws Throwable { features.add(feature); stepsToResult.put("background", result("undefined")); stepsToResult.put("step", result("undefined")); - stepDurationMillis = 0L; + stepDuration = ZERO; String actual = runFeaturesWithFormatter(false); assertXmlEqual("" + "" + "" + - " " + - " " + + " " + + " " + " " + " " + " " + @@ -212,13 +214,13 @@ public void testScenarioOutlineWithExamples() throws Throwable { " | 2 |\n"); features.add(feature); stepsToResult.put("step", result("undefined")); - stepDurationMillis = 0L; + stepDuration = ZERO; String actual = runFeaturesWithFormatter(false); assertXmlEqual("" + "" + "" + - " " + - " " + + " " + + " " + " " + " " + " " + @@ -248,13 +250,13 @@ public void testDurationCalculationOfStepsAndHooks() throws Throwable { stepsToResult.put("step", result("passed")); hooks.add(TestHelper.hookEntry("before", result("passed"))); hooks.add(TestHelper.hookEntry("after", result("passed"))); - stepDurationMillis = 1L; + stepDuration = ofMillis(1); String actual = runFeaturesWithFormatter(false); assertXmlEqual("" + "" + "" + - " " + - " " + + " " + + " " + " " + " " + " " + @@ -277,16 +279,16 @@ public void testScenarioWithFailedBeforeHook() throws Throwable { features.add(feature); stepsToResult.put("step", result("skipped")); hooks.add(TestHelper.hookEntry("before", result("failed", new TestNGException("message", "stacktrace")))); - stepDurationMillis = 0L; + stepDuration = ZERO; String actual = runFeaturesWithFormatter(false); assertXmlEqual("" + "" + "" + - " " + - " " + + " " + + " " + " " + " " + - " " + + " " + " " + @@ -309,16 +311,16 @@ public void testScenarioWithFailedAfterHook() throws Throwable { features.add(feature); stepsToResult.put("step", result("passed")); hooks.add(TestHelper.hookEntry("after", result("failed", new TestNGException("message", "stacktrace")))); - stepDurationMillis = 0L; + stepDuration = ZERO; String actual = runFeaturesWithFormatter(false); assertXmlEqual("" + "" + "" + - " " + - " " + + " " + + " " + " " + " " + - " " + + " " + " " + @@ -347,7 +349,7 @@ public int differenceFound(Difference difference) { private String runFeaturesWithFormatter(boolean strict) throws IOException { final File tempFile = File.createTempFile("cucumber-jvm-testng", ".xml"); - final TestNGFormatter formatter = new TestNGFormatter(toURL(tempFile.getAbsolutePath())); + final TestNGFormatter formatter = new TestNGFormatter(tempFile.toURI().toURL()); formatter.setStrict(strict); TestHelper.builder() @@ -358,7 +360,7 @@ private String runFeaturesWithFormatter(boolean strict) throws IOException { .withHooks(hooks) .withHookLocations(hookLocations) .withHookActions(hookActions) - .withTimeServiceIncrement(stepDurationMillis) + .withTimeServiceIncrement(stepDuration) .build() .run(); diff --git a/core/src/test/java/io/cucumber/core/plugin/TestUTF8OutputStreamWriter.java b/core/src/test/java/io/cucumber/core/plugin/TestUTF8OutputStreamWriter.java new file mode 100644 index 0000000000..53c6c46c38 --- /dev/null +++ b/core/src/test/java/io/cucumber/core/plugin/TestUTF8OutputStreamWriter.java @@ -0,0 +1,12 @@ +package io.cucumber.core.plugin; + + +import java.io.OutputStream; + +public class TestUTF8OutputStreamWriter { + + public static UTF8OutputStreamWriter create(OutputStream outputStream){ + return new UTF8OutputStreamWriter(outputStream); + } + +} \ No newline at end of file diff --git a/core/src/test/java/cucumber/runtime/formatter/TimelineFormatterTest.java b/core/src/test/java/io/cucumber/core/plugin/TimelineFormatterTest.java similarity index 93% rename from core/src/test/java/cucumber/runtime/formatter/TimelineFormatterTest.java rename to core/src/test/java/io/cucumber/core/plugin/TimelineFormatterTest.java index 97383a2890..bc516eabc7 100644 --- a/core/src/test/java/cucumber/runtime/formatter/TimelineFormatterTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/TimelineFormatterTest.java @@ -1,16 +1,20 @@ -package cucumber.runtime.formatter; +package io.cucumber.core.plugin; -import cucumber.api.Result; -import cucumber.runner.TestHelper; -import cucumber.runtime.model.CucumberFeature; +import io.cucumber.core.event.Result; +import io.cucumber.core.runner.TestHelper; +import io.cucumber.core.feature.CucumberFeature; import gherkin.deps.com.google.gson.Gson; import gherkin.deps.com.google.gson.GsonBuilder; +import gherkin.deps.com.google.gson.JsonDeserializer; + import org.junit.Before; import org.junit.Test; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.time.Duration; +import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; @@ -18,10 +22,9 @@ import java.util.List; import java.util.Map; import java.util.Scanner; -import java.util.concurrent.TimeUnit; -import static cucumber.runner.TestHelper.feature; -import static cucumber.runner.TestHelper.result; +import static io.cucumber.core.runner.TestHelper.feature; +import static io.cucumber.core.runner.TestHelper.result; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -36,11 +39,15 @@ public int compare(TimelineFormatter.TestData o1, TimelineFormatter.TestData o2) } }; - private static final String REPORT_TEMPLATE_RESOURCE_DIR = "src/main/resources/io/cucumber/formatter/timeline"; + private static final String REPORT_TEMPLATE_RESOURCE_DIR = "src/main/resources/io/cucumber/core/plugin/timeline"; private static final String REPORT_JS = "report.js"; - private static final long STEP_DURATION_MS = 1000; + private static final Duration STEP_DURATION = Duration.ofMillis(1000); - private final Gson gson = new GsonBuilder().create(); + private final Gson gson = new GsonBuilder().registerTypeAdapter(Instant.class, (JsonDeserializer) (json, + type, jsonDeserializationContext) -> { + return json.isJsonObject() ? Instant.ofEpochSecond(json.getAsJsonObject().get("seconds").getAsLong()) : Instant.ofEpochMilli(json.getAsLong()); + }).create(); + private final Map stepsToResult = new HashMap<>(); private final Map stepsToLocation = new HashMap<>(); @@ -128,7 +135,7 @@ public void shouldWriteItemsCorrectlyToReportJsWhenRunInParallel() throws Throwa .withRuntimeArgs("--plugin", "timeline:" + reportDir.getAbsolutePath(), "--threads", "3") .withStepsToResult(stepsToResult) .withStepsToLocation(stepsToLocation) - .withTimeServiceIncrement(TimeUnit.MILLISECONDS.toNanos(STEP_DURATION_MS)) + .withTimeServiceIncrement(STEP_DURATION) .build() .run(); @@ -233,7 +240,7 @@ private void runFormatterWithPlugin() { .withRuntimeArgs("--plugin", "timeline:" + reportDir.getAbsolutePath()) .withStepsToResult(stepsToResult) .withStepsToLocation(stepsToLocation) - .withTimeServiceIncrement(STEP_DURATION_MS) + .withTimeServiceIncrement(STEP_DURATION) .build() .run(); } diff --git a/core/src/test/java/cucumber/runtime/formatter/URLOutputStreamTest.java b/core/src/test/java/io/cucumber/core/plugin/URLOutputStreamTest.java similarity index 78% rename from core/src/test/java/cucumber/runtime/formatter/URLOutputStreamTest.java rename to core/src/test/java/io/cucumber/core/plugin/URLOutputStreamTest.java index 615836a190..b5bd2fbfb1 100644 --- a/core/src/test/java/cucumber/runtime/formatter/URLOutputStreamTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/URLOutputStreamTest.java @@ -1,20 +1,10 @@ -package cucumber.runtime.formatter; +package io.cucumber.core.plugin; -import cucumber.runtime.Utils; -import cucumber.util.FixJava; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.webbitserver.HttpControl; -import org.webbitserver.HttpHandler; -import org.webbitserver.HttpRequest; -import org.webbitserver.HttpResponse; -import org.webbitserver.WebServer; -import org.webbitserver.netty.NettyWebServer; -import org.webbitserver.rest.Rest; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -23,10 +13,12 @@ import java.io.Reader; import java.io.Writer; import java.net.InetSocketAddress; +import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -37,12 +29,32 @@ import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.webbitserver.HttpControl; +import org.webbitserver.HttpHandler; +import org.webbitserver.HttpRequest; +import org.webbitserver.HttpResponse; +import org.webbitserver.WebServer; +import org.webbitserver.netty.NettyWebServer; +import org.webbitserver.rest.Rest; public class URLOutputStreamTest { + private static final URL CUCUMBER_STEPDEFS = createUrl("http://localhost:9873/.cucumber/stepdefs.json"); + + private static URL createUrl(String s) { + try { + return new URL(s); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + private WebServer webbit; private final int threadsCount = 100; private final long waitTimeoutMillis = 30000L; @@ -66,38 +78,39 @@ public void stopWebbit() throws ExecutionException, InterruptedException { public void write_to_file_without_existing_parent_directory() throws IOException, URISyntaxException { Path filesWithoutParent = Files.createTempDirectory("filesWithoutParent"); String baseURL = filesWithoutParent.toUri().toURL().toString(); - URL urlWithoutParentDirectory = new URL(baseURL + "/non/existing/directory"); + URL urlWithoutParentDirectory = createUrl(baseURL + "/non/existing/directory"); - Writer w = new UTF8OutputStreamWriter(new URLOutputStream(urlWithoutParentDirectory)); + + Writer w = TestUTF8OutputStreamWriter.create(new URLOutputStream(urlWithoutParentDirectory)); w.write("Hellesøy"); w.close(); File testFile = new File(urlWithoutParentDirectory.toURI()); - assertEquals("Hellesøy", FixJava.readReader(openUTF8FileReader(testFile))); + assertEquals("Hellesøy", read(testFile)); } @Test public void can_write_to_file() throws IOException { File tmp = File.createTempFile("cucumber-jvm", "tmp"); - Writer w = new UTF8OutputStreamWriter(new URLOutputStream(tmp.toURI().toURL())); + Writer w = TestUTF8OutputStreamWriter.create(new URLOutputStream(tmp.toURI().toURL())); w.write("Hellesøy"); w.close(); - assertEquals("Hellesøy", FixJava.readReader(openUTF8FileReader(tmp))); + assertEquals("Hellesøy", read(tmp)); } @Test - public void can_http_put() throws IOException, ExecutionException, InterruptedException { + public void can_http_put() throws IOException, InterruptedException { final BlockingQueue data = new LinkedBlockingDeque(); Rest r = new Rest(webbit); r.PUT("/.cucumber/stepdefs.json", new HttpHandler() { @Override - public void handleHttpRequest(HttpRequest req, HttpResponse res, HttpControl ctl) throws Exception { + public void handleHttpRequest(HttpRequest req, HttpResponse res, HttpControl ctl) { data.offer(req.body()); res.end(); } }); - Writer w = new UTF8OutputStreamWriter(new URLOutputStream(new URL(Utils.toURL("http://localhost:9873/.cucumber"), "stepdefs.json"))); + Writer w = TestUTF8OutputStreamWriter.create(new URLOutputStream(CUCUMBER_STEPDEFS)); w.write("Hellesøy"); w.flush(); w.close(); @@ -105,8 +118,8 @@ public void handleHttpRequest(HttpRequest req, HttpResponse res, HttpControl ctl } @Test - public void throws_fnfe_if_http_response_is_404() throws IOException, ExecutionException, InterruptedException { - Writer w = new UTF8OutputStreamWriter(new URLOutputStream(new URL(Utils.toURL("http://localhost:9873/.cucumber"), "stepdefs.json"))); + public void throws_fnfe_if_http_response_is_404() throws IOException { + Writer w = TestUTF8OutputStreamWriter.create(new URLOutputStream(CUCUMBER_STEPDEFS)); w.write("Hellesøy"); w.flush(); try { @@ -117,18 +130,18 @@ public void throws_fnfe_if_http_response_is_404() throws IOException, ExecutionE } @Test - public void throws_ioe_if_http_response_is_500() throws IOException, ExecutionException, InterruptedException { + public void throws_ioe_if_http_response_is_500() throws IOException { Rest r = new Rest(webbit); r.PUT("/.cucumber/stepdefs.json", new HttpHandler() { @Override - public void handleHttpRequest(HttpRequest req, HttpResponse res, HttpControl ctl) throws Exception { + public void handleHttpRequest(HttpRequest req, HttpResponse res, HttpControl ctl) { res.status(500); res.content("something went wrong"); res.end(); } }); - Writer w = new UTF8OutputStreamWriter(new URLOutputStream(new URL(Utils.toURL("http://localhost:9873/.cucumber"), "stepdefs.json"))); + Writer w = TestUTF8OutputStreamWriter.create(new URLOutputStream(CUCUMBER_STEPDEFS)); w.write("Hellesøy"); w.flush(); try { @@ -136,7 +149,7 @@ public void handleHttpRequest(HttpRequest req, HttpResponse res, HttpControl ctl fail(); } catch (IOException expected) { assertEquals("PUT http://localhost:9873/.cucumber/stepdefs.json\n" + - "HTTP 500\nsomething went wrong", expected.getMessage()); + "HTTP 500\nsomething went wrong", expected.getMessage()); } } @@ -151,8 +164,16 @@ public void do_not_throw_ioe_if_parent_dir_created_by_another_thread() { assertTrue("Some thread get error during work. Error list:" + threadErrors.toString(), threadErrors.isEmpty()); } + private String read(File testFile) throws IOException { + String result; + try (BufferedReader br = new BufferedReader(openUTF8FileReader(testFile))) { + result = br.lines().collect(Collectors.joining(System.lineSeparator())); + } + return result; + } + private Reader openUTF8FileReader(final File file) throws IOException { - return new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8")); + return new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8); } private List getThreadsWithLatchForFile(final CountDownLatch countDownLatch, int threadsCount) { @@ -162,8 +183,8 @@ private List getThreadsWithLatchForFile(final CountDownLatch countDownLa final int curThreadNo = i; // It useful when 2-3 threads (not more) tries to create the same directory for the report final File tmp = (i % 3 == 0 || i % 3 == 2) ? - new File(tempFolder.getRoot().getAbsolutePath() + "/cuce" + ballast + i + "/tmpFile.tmp") : - new File(tempFolder.getRoot().getAbsolutePath() + "/cuce" + ballast + (i - 1) + "/tmpFile.tmp"); + new File(tempFolder.getRoot().getAbsolutePath() + "/cuce" + ballast + i + "/tmpFile.tmp") : + new File(tempFolder.getRoot().getAbsolutePath() + "/cuce" + ballast + (i - 1) + "/tmpFile.tmp"); tmpFiles.add(tmp); result.add(new Thread() { @Override diff --git a/core/src/test/java/cucumber/runtime/UndefinedStepsTrackerTest.java b/core/src/test/java/io/cucumber/core/plugin/UndefinedStepsTrackerTest.java similarity index 77% rename from core/src/test/java/cucumber/runtime/UndefinedStepsTrackerTest.java rename to core/src/test/java/io/cucumber/core/plugin/UndefinedStepsTrackerTest.java index e1ec91fa3d..61d05ef089 100644 --- a/core/src/test/java/cucumber/runtime/UndefinedStepsTrackerTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/UndefinedStepsTrackerTest.java @@ -1,23 +1,25 @@ -package cucumber.runtime; - -import cucumber.runner.TestHelper; -import cucumber.runner.TimeServiceEventBus; -import cucumber.runner.EventBus; -import cucumber.runner.TimeServiceStub; -import cucumber.runtime.model.CucumberFeature; -import gherkin.pickles.PickleLocation; -import org.junit.Ignore; -import org.junit.Test; - -import java.io.IOException; -import java.util.Collections; -import java.util.List; +package io.cucumber.core.plugin; +import static java.time.Duration.ZERO; import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import io.cucumber.core.event.SnippetsSuggestedEvent; +import org.junit.Test; + +import io.cucumber.core.event.TestSourceRead; +import io.cucumber.core.eventbus.EventBus; +import io.cucumber.core.feature.CucumberFeature; +import io.cucumber.core.runner.TestHelper; +import io.cucumber.core.runtime.TimeServiceEventBus; +import io.cucumber.core.runner.ClockStub; + public class UndefinedStepsTrackerTest { @Test @@ -43,7 +45,7 @@ public void removes_duplicates() { @Test public void uses_given_when_then_keywords() throws IOException { - EventBus bus = new TimeServiceEventBus(new TimeServiceStub(0)); + EventBus bus = new TimeServiceEventBus(new ClockStub(ZERO)); UndefinedStepsTracker tracker = new UndefinedStepsTracker(); tracker.setEventPublisher(bus); CucumberFeature feature = TestHelper.feature("path/test.feature", "" + @@ -51,14 +53,14 @@ public void uses_given_when_then_keywords() throws IOException { " Scenario: scenario name\n" + " Given A\n" + " Then B\n"); - feature.sendTestSourceRead(bus); + sendTestSourceRead(bus, feature); tracker.handleSnippetsSuggested(uri("file:path/test.feature"), locations(line(4)), asList("**KEYWORD** ^B$")); assertEquals("[Then ^B$]", tracker.getSnippets().toString()); } @Test public void converts_and_to_previous_step_keyword() throws IOException { - EventBus bus = new TimeServiceEventBus(new TimeServiceStub(0)); + EventBus bus = new TimeServiceEventBus(new ClockStub(ZERO)); UndefinedStepsTracker tracker = new UndefinedStepsTracker(); tracker.setEventPublisher(bus); CucumberFeature feature = TestHelper.feature("path/test.feature", "" + @@ -67,14 +69,14 @@ public void converts_and_to_previous_step_keyword() throws IOException { " When A\n" + " And B\n" + " But C\n"); - feature.sendTestSourceRead(bus); + sendTestSourceRead(bus, feature); tracker.handleSnippetsSuggested(uri("file:path/test.feature"), locations(line(5)), asList("**KEYWORD** ^C$")); assertEquals("[When ^C$]", tracker.getSnippets().toString()); } @Test public void backtrack_into_background_to_find_step_keyword() throws IOException { - EventBus bus = new TimeServiceEventBus(new TimeServiceStub(0)); + EventBus bus = new TimeServiceEventBus(new ClockStub(ZERO)); UndefinedStepsTracker tracker = new UndefinedStepsTracker(); tracker.setEventPublisher(bus); CucumberFeature feature = TestHelper.feature("path/test.feature", "" + @@ -84,14 +86,18 @@ public void backtrack_into_background_to_find_step_keyword() throws IOException " Scenario: scenario name\n" + " And B\n" + " But C\n"); - feature.sendTestSourceRead(bus); + sendTestSourceRead(bus, feature); tracker.handleSnippetsSuggested(uri("file:path/test.feature"), locations(line(5)), asList("**KEYWORD** ^C$")); assertEquals("[When ^C$]", tracker.getSnippets().toString()); } + private void sendTestSourceRead(EventBus bus, CucumberFeature feature) { + bus.send(new TestSourceRead(bus.getInstant(), feature.getUri().toString(), feature.getSource())); + } + @Test public void doesnt_try_to_use_star_keyword() throws IOException { - EventBus bus = new TimeServiceEventBus(new TimeServiceStub(0)); + EventBus bus = new TimeServiceEventBus(new ClockStub(ZERO)); UndefinedStepsTracker tracker = new UndefinedStepsTracker(); tracker.setEventPublisher(bus); CucumberFeature feature = TestHelper.feature("path/test.feature", "" + @@ -100,28 +106,28 @@ public void doesnt_try_to_use_star_keyword() throws IOException { " When A\n" + " And B\n" + " * C\n"); - feature.sendTestSourceRead(bus); + sendTestSourceRead(bus, feature); tracker.handleSnippetsSuggested(uri("file:path/test.feature"), locations(line(5)), asList("**KEYWORD** ^C$")); assertEquals("[When ^C$]", tracker.getSnippets().toString()); } @Test public void star_keyword_becomes_given_when_no_previous_step() throws IOException { - EventBus bus = new TimeServiceEventBus(new TimeServiceStub(0)); + EventBus bus = new TimeServiceEventBus(new ClockStub(ZERO)); UndefinedStepsTracker tracker = new UndefinedStepsTracker(); tracker.setEventPublisher(bus); CucumberFeature feature = TestHelper.feature("path/test.feature", "" + "Feature: feature name\n" + " Scenario: scenario name\n" + " * A\n"); - feature.sendTestSourceRead(bus); + sendTestSourceRead(bus, feature); tracker.handleSnippetsSuggested(uri("path/test.feature"), locations(line(3)), asList("**KEYWORD** ^A$")); assertEquals("[Given ^A$]", tracker.getSnippets().toString()); } @Test public void snippets_are_generated_for_correct_locale() throws Exception { - EventBus bus = new TimeServiceEventBus(new TimeServiceStub(0)); + EventBus bus = new TimeServiceEventBus(new ClockStub(ZERO)); UndefinedStepsTracker tracker = new UndefinedStepsTracker(); tracker.setEventPublisher(bus); CucumberFeature feature = TestHelper.feature("path/test.feature", "" + @@ -129,17 +135,17 @@ public void snippets_are_generated_for_correct_locale() throws Exception { "ФункциÑ:\n" + " Сценарий: \n" + " * Б\n"); - feature.sendTestSourceRead(bus); + sendTestSourceRead(bus, feature); tracker.handleSnippetsSuggested(uri("file:path/test.feature"), locations(line(4)), asList("**KEYWORD** ^Б$")); assertEquals("[ДопуÑтим ^Б$]", tracker.getSnippets().toString()); } - private List locations(int line) { - return asList(new PickleLocation(line, 0)); + private List locations(int line) { + return asList(new SnippetsSuggestedEvent.Location(line, 0)); } - private List locations() { - return Collections.emptyList(); + private List locations() { + return Collections.emptyList(); } private String uri() { diff --git a/core/src/test/java/cucumber/runtime/formatter/UnusedStepsSummaryPrinterTest.java b/core/src/test/java/io/cucumber/core/plugin/UnusedStepsSummaryPrinterTest.java similarity index 53% rename from core/src/test/java/cucumber/runtime/formatter/UnusedStepsSummaryPrinterTest.java rename to core/src/test/java/io/cucumber/core/plugin/UnusedStepsSummaryPrinterTest.java index 9ef70f38c0..3a7abaae09 100644 --- a/core/src/test/java/cucumber/runtime/formatter/UnusedStepsSummaryPrinterTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/UnusedStepsSummaryPrinterTest.java @@ -1,12 +1,17 @@ -package cucumber.runtime.formatter; - -import cucumber.api.TestStep; -import cucumber.api.event.StepDefinedEvent; -import cucumber.api.event.TestRunFinished; -import cucumber.api.event.TestStepFinished; -import cucumber.runner.TimeService; -import cucumber.runner.TimeServiceEventBus; -import cucumber.runtime.StepDefinition; +package io.cucumber.core.plugin; + +import java.time.Clock; +import java.time.Duration; + +import io.cucumber.core.event.Result; +import io.cucumber.core.event.Status; +import io.cucumber.core.event.StepDefinedEvent; +import io.cucumber.core.event.TestCase; +import io.cucumber.core.event.TestRunFinished; +import io.cucumber.core.event.TestStep; +import io.cucumber.core.event.TestStepFinished; +import io.cucumber.core.backend.StepDefinition; +import io.cucumber.core.runtime.TimeServiceEventBus; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -18,14 +23,14 @@ public class UnusedStepsSummaryPrinterTest { public void verifyUnusedStepsPrinted() { StringBuilder out = new StringBuilder(); UnusedStepsSummaryPrinter summaryPrinter = new UnusedStepsSummaryPrinter(out); - TimeServiceEventBus bus = new TimeServiceEventBus(TimeService.SYSTEM); + TimeServiceEventBus bus = new TimeServiceEventBus(Clock.systemUTC()); summaryPrinter.setEventPublisher(bus); // Register two steps, use one, then finish the test run - bus.send(new StepDefinedEvent(bus.getTime(), bus.getTimeMillis(), mockStepDef("my/belly.feature:3", "a few cukes"))); - bus.send(new StepDefinedEvent(bus.getTime(), bus.getTimeMillis(), mockStepDef("my/tummy.feature:5", "some more cukes"))); - bus.send(new TestStepFinished(bus.getTime(), bus.getTimeMillis(), null, mockTestStep("my/belly.feature:3"), null)); - bus.send(new TestRunFinished(bus.getTime(), bus.getTimeMillis())); + bus.send(new StepDefinedEvent(bus.getInstant(), mockStepDef("my/belly.feature:3", "a few cukes"))); + bus.send(new StepDefinedEvent(bus.getInstant(), mockStepDef("my/tummy.feature:5", "some more cukes"))); + bus.send(new TestStepFinished(bus.getInstant(), mock(TestCase.class), mockTestStep("my/belly.feature:3"), new Result(Status.UNUSED, Duration.ZERO, null))); + bus.send(new TestRunFinished(bus.getInstant())); // Verify produced output assertEquals("1 Unused steps:\n" + "my/tummy.feature:5 # some more cukes\n", out.toString()); diff --git a/core/src/test/java/cucumber/runtime/formatter/UsageFormatterTest.java b/core/src/test/java/io/cucumber/core/plugin/UsageFormatterTest.java similarity index 75% rename from core/src/test/java/cucumber/runtime/formatter/UsageFormatterTest.java rename to core/src/test/java/io/cucumber/core/plugin/UsageFormatterTest.java index e4bee8edaa..90cbe6e1ec 100644 --- a/core/src/test/java/cucumber/runtime/formatter/UsageFormatterTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/UsageFormatterTest.java @@ -1,17 +1,18 @@ -package cucumber.runtime.formatter; - -import cucumber.api.PickleStepTestStep; -import cucumber.api.Result; -import cucumber.api.TestCase; -import cucumber.api.TestStep; -import cucumber.api.event.TestStepFinished; +package io.cucumber.core.plugin; + +import io.cucumber.core.event.PickleStepTestStep; +import io.cucumber.core.event.Result; +import io.cucumber.core.event.Status; +import io.cucumber.core.event.TestCase; +import io.cucumber.core.event.TestStep; +import io.cucumber.core.event.TestStepFinished; import org.junit.Test; import org.mockito.Mockito; import java.io.Closeable; import java.io.IOException; -import java.math.BigDecimal; -import java.util.Arrays; +import java.time.Duration; +import java.time.Instant; import java.util.Collections; import java.util.List; import java.util.Map; @@ -40,8 +41,8 @@ public void close() throws IOException { public void resultWithFailedStep() { Appendable out = mock(Appendable.class); UsageFormatter usageFormatter = new UsageFormatter(out); - Result result = new Result(Result.Type.FAILED, 0L, null); - usageFormatter.handleTestStepFinished(new TestStepFinished(0L, 0L, mock(TestCase.class), mockTestStep(), result)); + Result result = new Result(Status.FAILED, Duration.ZERO, null); + usageFormatter.handleTestStepFinished(new TestStepFinished(Instant.EPOCH, mock(TestCase.class), mockTestStep(), result)); verifyZeroInteractions(out); } @@ -49,8 +50,8 @@ public void resultWithFailedStep() { public void resultWithSkippedStep() { Appendable out = mock(Appendable.class); UsageFormatter usageFormatter = new UsageFormatter(out); - Result result = new Result(Result.Type.SKIPPED, 0L, null); - usageFormatter.handleTestStepFinished(new TestStepFinished(0L, 0L, mock(TestCase.class), mockTestStep(), result)); + Result result = new Result(Status.SKIPPED, Duration.ZERO, null); + usageFormatter.handleTestStepFinished(new TestStepFinished(Instant.EPOCH, mock(TestCase.class), mockTestStep(), result)); verifyZeroInteractions(out); } @@ -58,8 +59,8 @@ public void resultWithSkippedStep() { public void resultWithPendingStep() { Appendable out = mock(Appendable.class); UsageFormatter usageFormatter = new UsageFormatter(out); - Result result = new Result(Result.Type.PENDING, 0L, null); - usageFormatter.handleTestStepFinished(new TestStepFinished(0L, 0L, mock(TestCase.class), mockTestStep(), result)); + Result result = new Result(Status.PENDING, Duration.ZERO, null); + usageFormatter.handleTestStepFinished(new TestStepFinished(Instant.EPOCH, mock(TestCase.class), mockTestStep(), result)); verifyZeroInteractions(out); } @@ -67,8 +68,8 @@ public void resultWithPendingStep() { public void resultWithAmbiguousStep() { Appendable out = mock(Appendable.class); UsageFormatter usageFormatter = new UsageFormatter(out); - Result result = new Result(Result.Type.AMBIGUOUS, 0L, null); - usageFormatter.handleTestStepFinished(new TestStepFinished(0L, mock(TestCase.class), mockTestStep(), result)); + Result result = new Result(Status.AMBIGUOUS, Duration.ZERO, null); + usageFormatter.handleTestStepFinished(new TestStepFinished(Instant.EPOCH, mock(TestCase.class), mockTestStep(), result)); verifyZeroInteractions(out); } @@ -76,8 +77,8 @@ public void resultWithAmbiguousStep() { public void resultWithUndefinedStep() { Appendable out = mock(Appendable.class); UsageFormatter usageFormatter = new UsageFormatter(out); - Result result = new Result(Result.Type.AMBIGUOUS, 0L, null); - usageFormatter.handleTestStepFinished(new TestStepFinished(0L, 0L, mock(TestCase.class), mockTestStep(), result)); + Result result = new Result(Status.AMBIGUOUS, Duration.ZERO, null); + usageFormatter.handleTestStepFinished(new TestStepFinished(Instant.EPOCH, mock(TestCase.class), mockTestStep(), result)); verifyZeroInteractions(out); } @@ -86,10 +87,10 @@ public void resultWithPassedStep() { Appendable out = mock(Appendable.class); UsageFormatter usageFormatter = new UsageFormatter(out); TestStep testStep = mockTestStep(); - Result result = new Result(Result.Type.PASSED, 12345L, null); + Result result = new Result(Status.PASSED, Duration.ofNanos(12345L), null); - usageFormatter.handleTestStepFinished(new TestStepFinished(0L, 0L, mock(TestCase.class), testStep, result)); + usageFormatter.handleTestStepFinished(new TestStepFinished(Instant.EPOCH, mock(TestCase.class), testStep, result)); Map> usageMap = usageFormatter.usageMap; assertEquals(usageMap.size(), 1); @@ -97,7 +98,7 @@ public void resultWithPassedStep() { assertEquals(durationEntries.size(), 1); assertEquals(durationEntries.get(0).getName(), "step"); assertEquals(durationEntries.get(0).getDurations().size(), 1); - assertEquals(durationEntries.get(0).getDurations().get(0).getDuration(), new BigDecimal("0.000012345")); + assertEquals(durationEntries.get(0).getDurations().get(0).getDuration(), Duration.ofNanos(12345L)); } @Test @@ -106,11 +107,11 @@ public void resultWithPassedAndFailedStep() { UsageFormatter usageFormatter = new UsageFormatter(out); TestStep testStep = mockTestStep(); - Result passed = new Result(Result.Type.PASSED, 12345L, null); - usageFormatter.handleTestStepFinished(new TestStepFinished(0L, 0L, mock(TestCase.class), testStep, passed)); + Result passed = new Result(Status.PASSED, Duration.ofSeconds(12345L), null); + usageFormatter.handleTestStepFinished(new TestStepFinished(Instant.EPOCH, mock(TestCase.class), testStep, passed)); - Result failed = new Result(Result.Type.FAILED, 0L, null); - usageFormatter.handleTestStepFinished(new TestStepFinished(0L, 0L, mock(TestCase.class), testStep, failed)); + Result failed = new Result(Status.FAILED, Duration.ZERO, null); + usageFormatter.handleTestStepFinished(new TestStepFinished(Instant.EPOCH, mock(TestCase.class), testStep, failed)); Map> usageMap = usageFormatter.usageMap; assertEquals(usageMap.size(), 1); @@ -118,7 +119,7 @@ public void resultWithPassedAndFailedStep() { assertEquals(durationEntries.size(), 1); assertEquals(durationEntries.get(0).getName(), "step"); assertEquals(durationEntries.get(0).getDurations().size(), 1); - assertEquals(durationEntries.get(0).getDurations().get(0).getDuration(), new BigDecimal("0.000012345")); + assertEquals(durationEntries.get(0).getDurations().get(0).getDuration(), Duration.ofSeconds(12345)); } @Test @@ -126,9 +127,9 @@ public void resultWithZeroDuration() { Appendable out = mock(Appendable.class); UsageFormatter usageFormatter = new UsageFormatter(out); TestStep testStep = mockTestStep(); - Result result = new Result(Result.Type.PASSED, 0L, null); + Result result = new Result(Status.PASSED, Duration.ZERO, null); - usageFormatter.handleTestStepFinished(new TestStepFinished(0L, 0L, mock(TestCase.class), testStep, result)); + usageFormatter.handleTestStepFinished(new TestStepFinished(Instant.EPOCH, mock(TestCase.class), testStep, result)); Map> usageMap = usageFormatter.usageMap; assertEquals(usageMap.size(), 1); @@ -136,7 +137,7 @@ public void resultWithZeroDuration() { assertEquals(durationEntries.size(), 1); assertEquals(durationEntries.get(0).getName(), "step"); assertEquals(durationEntries.get(0).getDurations().size(), 1); - assertEquals(durationEntries.get(0).getDurations().get(0).getDuration(), BigDecimal.ZERO); + assertEquals(durationEntries.get(0).getDurations().get(0).getDuration(), Duration.ZERO); } // Note: Duplicate of above test @@ -145,9 +146,9 @@ public void resultWithNullDuration() { Appendable out = mock(Appendable.class); UsageFormatter usageFormatter = new UsageFormatter(out); PickleStepTestStep testStep = mockTestStep(); - Result result = new Result(Result.Type.PASSED, 0L, null); + Result result = new Result(Status.PASSED, Duration.ZERO, null); - usageFormatter.handleTestStepFinished(new TestStepFinished(0L, 0L, mock(TestCase.class), testStep, result)); + usageFormatter.handleTestStepFinished(new TestStepFinished(Instant.EPOCH, mock(TestCase.class), testStep, result)); Map> usageMap = usageFormatter.usageMap; assertEquals(usageMap.size(), 1); @@ -155,7 +156,7 @@ public void resultWithNullDuration() { assertEquals(durationEntries.size(), 1); assertEquals(durationEntries.get(0).getName(), "step"); assertEquals(durationEntries.get(0).getDurations().size(), 1); - assertEquals(durationEntries.get(0).getDurations().get(0).getDuration(), BigDecimal.ZERO); + assertEquals(durationEntries.get(0).getDurations().get(0).getDuration(), Duration.ZERO); } @Test @@ -163,7 +164,7 @@ public void doneWithoutUsageStatisticStrategies() { StringBuffer out = new StringBuffer(); UsageFormatter usageFormatter = new UsageFormatter(out); UsageFormatter.StepContainer stepContainer = new UsageFormatter.StepContainer("a step"); - UsageFormatter.StepDuration stepDuration = new UsageFormatter.StepDuration(new BigDecimal("0.012345678"), "location.feature"); + UsageFormatter.StepDuration stepDuration = new UsageFormatter.StepDuration(Duration.ofNanos(12345678L), "location.feature"); stepContainer.getDurations().addAll(asList(stepDuration)); usageFormatter.usageMap.put("a (.*)", asList(stepContainer)); @@ -200,7 +201,7 @@ public void doneWithUsageStatisticStrategies() { UsageFormatter usageFormatter = new UsageFormatter(out); UsageFormatter.StepContainer stepContainer = new UsageFormatter.StepContainer("a step"); - UsageFormatter.StepDuration stepDuration = new UsageFormatter.StepDuration(new BigDecimal("0.012345678"), "location.feature"); + UsageFormatter.StepDuration stepDuration = new UsageFormatter.StepDuration(Duration.ofNanos(12345678L), "location.feature"); stepContainer.getDurations().addAll(asList(stepDuration)); usageFormatter.usageMap.put("a (.*)", asList(stepContainer)); @@ -237,56 +238,56 @@ public void doneWithUsageStatisticStrategies() { public void calculateAverageFromList() { Appendable out = mock(Appendable.class); UsageFormatter usageFormatter = new UsageFormatter(out); - BigDecimal result = usageFormatter.calculateAverage(Arrays.asList(BigDecimal.valueOf(1L), BigDecimal.valueOf(2L), BigDecimal.valueOf(3L))); - assertEquals(result.toString(), "2.000000000"); + Duration result = usageFormatter.calculateAverage(asList(Duration.ofSeconds(1L), Duration.ofSeconds(2L), Duration.ofSeconds(3L))); + assertEquals(Duration.ofSeconds(2L), result); } @Test public void calculateAverageOf() { Appendable out = mock(Appendable.class); UsageFormatter usageFormatter = new UsageFormatter(out); - BigDecimal result = usageFormatter.calculateAverage(Arrays.asList(BigDecimal.valueOf(1L), BigDecimal.valueOf(1L), BigDecimal.valueOf(2L))); - assertEquals(result, BigDecimal.valueOf(1.333333333)); + Duration result = usageFormatter.calculateAverage(asList(Duration.ofSeconds(1L), Duration.ofSeconds(1L), Duration.ofSeconds(2L))); + assertEquals(Duration.ofNanos(1333333333), result); } @Test public void calculateAverageOfEmptylist() { Appendable out = mock(Appendable.class); UsageFormatter usageFormatter = new UsageFormatter(out); - BigDecimal result = usageFormatter.calculateAverage(Collections.EMPTY_LIST); - assertEquals(result, BigDecimal.ZERO); + Duration result = usageFormatter.calculateAverage(Collections.emptyList()); + assertEquals(Duration.ZERO, result); } @Test public void calculateMedianOfOddNumberOfEntries() { Appendable out = mock(Appendable.class); UsageFormatter usageFormatter = new UsageFormatter(out); - BigDecimal result = usageFormatter.calculateMedian(Arrays.asList(BigDecimal.valueOf(1L), BigDecimal.valueOf(2L), BigDecimal.valueOf(3L))); - assertEquals(result, BigDecimal.valueOf(2L)); + Duration result = usageFormatter.calculateMedian(asList(Duration.ofSeconds(1L), Duration.ofSeconds(2L), Duration.ofSeconds(3L))); + assertEquals(Duration.ofSeconds(2L), result); } @Test public void calculateMedianOfEvenNumberOfEntries() { Appendable out = mock(Appendable.class); UsageFormatter usageFormatter = new UsageFormatter(out); - BigDecimal result = usageFormatter.calculateMedian(Arrays.asList(BigDecimal.valueOf(1L), BigDecimal.valueOf(3L), BigDecimal.valueOf(10L), BigDecimal.valueOf(5L))); - assertEquals(result.toString(), "4.000000000"); + Duration result = usageFormatter.calculateMedian(asList(Duration.ofSeconds(1L), Duration.ofSeconds(3L), Duration.ofSeconds(10L), Duration.ofSeconds(5L))); + assertEquals(Duration.ofSeconds(4), result); } @Test public void calculateMedianOf() { Appendable out = mock(Appendable.class); UsageFormatter usageFormatter = new UsageFormatter(out); - BigDecimal result = usageFormatter.calculateMedian(Arrays.asList(BigDecimal.valueOf(2L), BigDecimal.valueOf(9L))); - assertEquals(result.toString(), "5.500000000"); + Duration result = usageFormatter.calculateMedian(asList(Duration.ofSeconds(2L), Duration.ofSeconds(9L))); + assertEquals(Duration.ofMillis(5500), result); } @Test public void calculateMedianOfEmptylist() { Appendable out = mock(Appendable.class); UsageFormatter usageFormatter = new UsageFormatter(out); - BigDecimal result = usageFormatter.calculateMedian(Collections.EMPTY_LIST); - assertEquals(result, BigDecimal.ZERO); + Duration result = usageFormatter.calculateMedian(Collections.emptyList()); + assertEquals(Duration.ZERO, result); } private PickleStepTestStep mockTestStep() { diff --git a/core/src/test/java/cucumber/runtime/MethodFormatTest.java b/core/src/test/java/io/cucumber/core/reflection/MethodFormatTest.java similarity index 89% rename from core/src/test/java/cucumber/runtime/MethodFormatTest.java rename to core/src/test/java/io/cucumber/core/reflection/MethodFormatTest.java index c491117e64..7efccef4dc 100644 --- a/core/src/test/java/cucumber/runtime/MethodFormatTest.java +++ b/core/src/test/java/io/cucumber/core/reflection/MethodFormatTest.java @@ -1,4 +1,4 @@ -package cucumber.runtime; +package io.cucumber.core.reflection; import org.junit.Before; import org.junit.Test; @@ -41,6 +41,6 @@ public void shouldUseSimpleFormatWhenMethodHasNoException() { @Test public void prints_code_source() { String format = MethodFormat.FULL.format(methodWithoutArgs); - assertTrue(format.startsWith("cucumber.runtime.MethodFormatTest.methodWithoutArgs() in file:")); + assertTrue(format.startsWith("io.cucumber.core.reflection.MethodFormatTest.methodWithoutArgs() in file:")); } } diff --git a/core/src/test/java/io/cucumber/core/reflection/ReflectionsTest.java b/core/src/test/java/io/cucumber/core/reflection/ReflectionsTest.java new file mode 100644 index 0000000000..be7f9b74f8 --- /dev/null +++ b/core/src/test/java/io/cucumber/core/reflection/ReflectionsTest.java @@ -0,0 +1,25 @@ +package io.cucumber.core.reflection; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class ReflectionsTest { + @Test + public void public_non_static_inner_classes_are_not_instantiable() { + assertFalse(Reflections.isInstantiable(NonStaticInnerClass.class)); + } + + @Test + public void public_static_inner_classes_are_instantiable() { + assertTrue(Reflections.isInstantiable(StaticInnerClass.class)); + } + + public class NonStaticInnerClass { + } + + public static class StaticInnerClass { + } + +} diff --git a/core/src/test/java/cucumber/runner/AmbiguousStepDefinitionMatchsTest.java b/core/src/test/java/io/cucumber/core/runner/AmbiguousStepDefinitionMatchsTest.java similarity index 90% rename from core/src/test/java/cucumber/runner/AmbiguousStepDefinitionMatchsTest.java rename to core/src/test/java/io/cucumber/core/runner/AmbiguousStepDefinitionMatchsTest.java index 20c9190596..e46ea70baa 100644 --- a/core/src/test/java/cucumber/runner/AmbiguousStepDefinitionMatchsTest.java +++ b/core/src/test/java/io/cucumber/core/runner/AmbiguousStepDefinitionMatchsTest.java @@ -1,18 +1,17 @@ -package cucumber.runner; +package io.cucumber.core.runner; + +import static org.mockito.Mockito.mock; + +import java.util.Collections; -import cucumber.api.Scenario; -import gherkin.pickles.Argument; -import gherkin.pickles.PickleLocation; -import gherkin.pickles.PickleStep; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import java.util.Collections; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; +import gherkin.pickles.Argument; +import gherkin.pickles.PickleLocation; +import gherkin.pickles.PickleStep; +import io.cucumber.core.api.Scenario; public class AmbiguousStepDefinitionMatchsTest { diff --git a/core/src/test/java/io/cucumber/core/runner/CachingGlueTest.java b/core/src/test/java/io/cucumber/core/runner/CachingGlueTest.java new file mode 100644 index 0000000000..02130f799f --- /dev/null +++ b/core/src/test/java/io/cucumber/core/runner/CachingGlueTest.java @@ -0,0 +1,559 @@ +package io.cucumber.core.runner; + +import gherkin.pickles.*; +import io.cucumber.core.api.Scenario; +import io.cucumber.core.backend.*; +import io.cucumber.core.runtime.TimeServiceEventBus; +import io.cucumber.core.stepexpression.TypeRegistry; +import io.cucumber.cucumberexpressions.ParameterByTypeTransformer; +import io.cucumber.cucumberexpressions.ParameterType; +import io.cucumber.datatable.DataTable; +import io.cucumber.datatable.DataTableType; +import io.cucumber.datatable.TableCellByTypeTransformer; +import io.cucumber.datatable.TableEntryByTypeTransformer; +import org.junit.Test; + +import java.time.Clock; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static java.util.Collections.singletonList; +import static java.util.Locale.ENGLISH; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class CachingGlueTest { + + private final TypeRegistry typeRegistry = new TypeRegistry(ENGLISH); + private CachingGlue glue = new CachingGlue(new TimeServiceEventBus(Clock.systemUTC())); + + @Test + public void throws_duplicate_error_on_dupe_stepdefs() { + StepDefinition a = mock(StepDefinition.class); + when(a.getPattern()).thenReturn("hello"); + when(a.getLocation(true)).thenReturn("foo.bf:10"); + glue.addStepDefinition(a); + + StepDefinition b = mock(StepDefinition.class); + when(b.getPattern()).thenReturn("hello"); + when(b.getLocation(true)).thenReturn("bar.bf:90"); + glue.addStepDefinition(b); + + DuplicateStepDefinitionException exception = assertThrows( + DuplicateStepDefinitionException.class, + () -> glue.prepareGlue(typeRegistry) + ); + assertThat(exception.getMessage(), equalTo("Duplicate step definitions in foo.bf:10 and bar.bf:90")); + } + + @Test + public void throws_on_duplicate_default_parameter_transformer() { + glue.addDefaultParameterTransformer(new MockedDefaultParameterTransformer()); + glue.addDefaultParameterTransformer(new MockedDefaultParameterTransformer()); + + DuplicateDefaultParameterTransformers exception = assertThrows( + DuplicateDefaultParameterTransformers.class, + () -> glue.prepareGlue(typeRegistry) + ); + assertThat(exception.getMessage(), equalTo("" + + "There may not be more then one default parameter transformer. Found:\n" + + " - mocked default parameter transformer\n" + + " - mocked default parameter transformer\n" + )); + } + + @Test + public void throws_on_duplicate_default_table_entry_transformer() { + glue.addDefaultDataTableEntryTransformer(new MockedDefaultDataTableEntryTransformer()); + glue.addDefaultDataTableEntryTransformer(new MockedDefaultDataTableEntryTransformer()); + + DuplicateDefaultDataTableEntryTransformers exception = assertThrows( + DuplicateDefaultDataTableEntryTransformers.class, + () -> glue.prepareGlue(typeRegistry) + ); + assertThat(exception.getMessage(), equalTo("" + + "There may not be more then one default data table entry. Found:\n" + + " - mocked default data table entry transformer\n" + + " - mocked default data table entry transformer\n" + )); + } + + @Test + public void throws_on_duplicate_default_table_cell_transformer() { + glue.addDefaultDataTableCellTransformer(new MockedDefaultDataTableCellTransformer()); + glue.addDefaultDataTableCellTransformer(new MockedDefaultDataTableCellTransformer()); + + DuplicateDefaultDataTableCellTransformers exception = assertThrows( + DuplicateDefaultDataTableCellTransformers.class, + () -> glue.prepareGlue(typeRegistry) + ); + assertThat(exception.getMessage(), equalTo("" + + "There may not be more then one default table cell transformers. Found:\n" + + " - mocked default data table cell transformer\n" + + " - mocked default data table cell transformer\n" + )); + } + + + @Test + public void removes_glue_that_is_scenario_scoped() { + // This test is a bit fragile - it is testing state, not behaviour. + // But it was too much hassle creating a better test without refactoring RuntimeGlue + // and probably some of its immediate collaborators... Aslak. + + glue.addStepDefinition(new MockedScenarioScopedStepDefinition("pattern")); + glue.addBeforeHook(new MockedScenarioScopedHookDefinition()); + glue.addAfterHook(new MockedScenarioScopedHookDefinition()); + glue.addBeforeStepHook(new MockedScenarioScopedHookDefinition()); + glue.addAfterStepHook(new MockedScenarioScopedHookDefinition()); + glue.addDataTableType(new MockedDataTableTypeDefinition()); + glue.addParameterType(new MockedParameterTypeDefinition()); + glue.addDefaultParameterTransformer(new MockedDefaultParameterTransformer()); + glue.addDefaultDataTableCellTransformer(new MockedDefaultDataTableCellTransformer()); + glue.addDefaultDataTableEntryTransformer(new MockedDefaultDataTableEntryTransformer()); + + glue.prepareGlue(typeRegistry); + + assertEquals(1, glue.getStepDefinitions().size()); + assertEquals(1, glue.getBeforeHooks().size()); + assertEquals(1, glue.getAfterHooks().size()); + assertEquals(1, glue.getBeforeStepHooks().size()); + assertEquals(1, glue.getAfterStepHooks().size()); + assertEquals(1, glue.getDataTableTypeDefinitions().size()); + assertEquals(1, glue.getParameterTypeDefinitions().size()); + assertEquals(1, glue.getDefaultParameterTransformers().size()); + assertEquals(1, glue.getDefaultDataTableCellTransformers().size()); + assertEquals(1, glue.getDefaultDataTableEntryTransformers().size()); + + glue.removeScenarioScopedGlue(); + + assertEquals(0, glue.getStepDefinitions().size()); + assertEquals(0, glue.getBeforeHooks().size()); + assertEquals(0, glue.getAfterHooks().size()); + assertEquals(0, glue.getBeforeStepHooks().size()); + assertEquals(0, glue.getAfterStepHooks().size()); + assertEquals(0, glue.getDataTableTypeDefinitions().size()); + assertEquals(0, glue.getParameterTypeDefinitions().size()); + assertEquals(0, glue.getDefaultParameterTransformers().size()); + assertEquals(0, glue.getDefaultDataTableCellTransformers().size()); + assertEquals(0, glue.getDefaultDataTableEntryTransformers().size()); + } + + @Test + public void returns_null_if_no_matching_steps_found() { + StepDefinition stepDefinition = new MockedStepDefinition("pattern1"); + glue.addStepDefinition(stepDefinition); + + String featurePath = "someFeature.feature"; + + PickleStep pickleStep = getPickleStep("pattern"); + assertNull(glue.stepDefinitionMatch(featurePath, pickleStep)); + } + + @Test + public void returns_match_from_cache_if_single_found() { + StepDefinition stepDefinition1 = new MockedStepDefinition("^pattern1"); + StepDefinition stepDefinition2 = new MockedStepDefinition("^pattern2"); + glue.addStepDefinition(stepDefinition1); + glue.addStepDefinition(stepDefinition2); + glue.prepareGlue(typeRegistry); + + String featurePath = "someFeature.feature"; + String stepText = "pattern1"; + + PickleStep pickleStep1 = getPickleStep(stepText); + + PickleStepDefinitionMatch pickleStepDefinitionMatch = glue.stepDefinitionMatch(featurePath, pickleStep1); + assertEquals(stepDefinition1, pickleStepDefinitionMatch.getStepDefinition()); + + + //check cache + assertEquals(stepDefinition1.getPattern(), glue.getStepPatternByStepText().get(stepText)); + CoreStepDefinition coreStepDefinition = glue.getStepDefinitionsByPattern().get(stepDefinition1.getPattern()); + assertEquals(stepDefinition1, coreStepDefinition.getStepDefinition()); + + PickleStep pickleStep2 = getPickleStep(stepText); + PickleStepDefinitionMatch pickleStepDefinitionMatch2 = glue.stepDefinitionMatch(featurePath, pickleStep2); + assertEquals(stepDefinition1, pickleStepDefinitionMatch2.getStepDefinition()); + } + + @Test + public void returns_match_from_cache_for_step_with_table() { + StepDefinition stepDefinition1 = new MockedStepDefinition("^pattern1"); + StepDefinition stepDefinition2 = new MockedStepDefinition("^pattern2"); + glue.addStepDefinition(stepDefinition1); + glue.addStepDefinition(stepDefinition2); + glue.prepareGlue(typeRegistry); + + String featurePath = "someFeature.feature"; + String stepText = "pattern1"; + + PickleStep pickleStep1 = getPickleStepWithSingleCellTable(stepText, "cell 1"); + PickleStepDefinitionMatch match1 = glue.stepDefinitionMatch(featurePath, pickleStep1); + assertEquals(stepDefinition1, match1.getStepDefinition()); + + //check cache + assertEquals(stepDefinition1.getPattern(), glue.getStepPatternByStepText().get(stepText)); + CoreStepDefinition coreStepDefinition = glue.getStepDefinitionsByPattern().get(stepDefinition1.getPattern()); + assertEquals(stepDefinition1, coreStepDefinition.getStepDefinition()); + + //check arguments + assertEquals("cell 1", ((DataTable) match1.getArguments().get(0).getValue()).cell(0, 0)); + + //check second match + PickleStep pickleStep2 = getPickleStepWithSingleCellTable(stepText, "cell 2"); + PickleStepDefinitionMatch match2 = glue.stepDefinitionMatch(featurePath, pickleStep2); + + //check arguments + assertEquals("cell 2", ((DataTable) match2.getArguments().get(0).getValue()).cell(0, 0)); + } + + @Test + public void returns_match_from_cache_for_ste_with_doc_string() { + StepDefinition stepDefinition1 = new MockedStepDefinition("^pattern1"); + StepDefinition stepDefinition2 = new MockedStepDefinition("^pattern2"); + glue.addStepDefinition(stepDefinition1); + glue.addStepDefinition(stepDefinition2); + glue.prepareGlue(typeRegistry); + + String featurePath = "someFeature.feature"; + String stepText = "pattern1"; + + PickleStep pickleStep1 = getPickleStepWithDocString(stepText, "doc string 1"); + + PickleStepDefinitionMatch match1 = glue.stepDefinitionMatch(featurePath, pickleStep1); + assertEquals(stepDefinition1, match1.getStepDefinition()); + + //check cache + assertEquals(stepDefinition1.getPattern(), glue.getStepPatternByStepText().get(stepText)); + CoreStepDefinition coreStepDefinition = glue.getStepDefinitionsByPattern().get(stepDefinition1.getPattern()); + assertEquals(stepDefinition1, coreStepDefinition.getStepDefinition()); + + //check arguments + assertEquals("doc string 1", match1.getArguments().get(0).getValue()); + + //check second match + PickleStep pickleStep2 = getPickleStepWithDocString(stepText, "doc string 2"); + PickleStepDefinitionMatch match2 = glue.stepDefinitionMatch(featurePath, pickleStep2); + //check arguments + assertEquals("doc string 2", match2.getArguments().get(0).getValue()); + } + + @Test + public void returns_fresh_match_from_cache_after_evicting_scenario_scoped() { + String featurePath = "someFeature.feature"; + String stepText = "pattern1"; + PickleStep pickleStep1 = getPickleStep(stepText); + + + StepDefinition stepDefinition1 = new MockedScenarioScopedStepDefinition("^pattern1"); + glue.addStepDefinition(stepDefinition1); + glue.prepareGlue(typeRegistry); + + + PickleStepDefinitionMatch pickleStepDefinitionMatch = glue.stepDefinitionMatch(featurePath, pickleStep1); + assertEquals(stepDefinition1, pickleStepDefinitionMatch.getStepDefinition()); + + glue.removeScenarioScopedGlue(); + + StepDefinition stepDefinition2 = new MockedScenarioScopedStepDefinition("^pattern1"); + glue.addStepDefinition(stepDefinition2); + glue.prepareGlue(typeRegistry); + + PickleStepDefinitionMatch pickleStepDefinitionMatch2 = glue.stepDefinitionMatch(featurePath, pickleStep1); + assertEquals(stepDefinition2, pickleStepDefinitionMatch2.getStepDefinition()); + } + + + @Test + public void returns_no_match_after_evicting_scenario_scoped() { + String featurePath = "someFeature.feature"; + String stepText = "pattern1"; + PickleStep pickleStep1 = getPickleStep(stepText); + + + StepDefinition stepDefinition1 = new MockedScenarioScopedStepDefinition("^pattern1"); + glue.addStepDefinition(stepDefinition1); + glue.prepareGlue(typeRegistry); + + + PickleStepDefinitionMatch pickleStepDefinitionMatch = glue.stepDefinitionMatch(featurePath, pickleStep1); + assertEquals(stepDefinition1, pickleStepDefinitionMatch.getStepDefinition()); + + glue.removeScenarioScopedGlue(); + + glue.prepareGlue(typeRegistry); + + PickleStepDefinitionMatch pickleStepDefinitionMatch2 = glue.stepDefinitionMatch(featurePath, pickleStep1); + assertThat(pickleStepDefinitionMatch2, nullValue()); + } + + private static PickleStep getPickleStepWithSingleCellTable(String stepText, String cell) { + return new PickleStep(stepText, Collections.singletonList(new PickleTable(singletonList(new PickleRow(singletonList(new PickleCell(mock(PickleLocation.class), cell)))))), Collections.emptyList()); + } + + private static PickleStep getPickleStepWithDocString(String stepText, String doc) { + return new PickleStep(stepText, Collections.singletonList(new PickleString(mock(PickleLocation.class), doc)), Collections.emptyList()); + } + + @Test + public void throws_ambiguous_steps_def_exception_when_many_patterns_match() { + StepDefinition stepDefinition1 = new MockedStepDefinition("pattern1"); + StepDefinition stepDefinition2 = new MockedStepDefinition("^pattern2"); + StepDefinition stepDefinition3 = new MockedStepDefinition("^pattern[1,3]"); + glue.addStepDefinition(stepDefinition1); + glue.addStepDefinition(stepDefinition2); + glue.addStepDefinition(stepDefinition3); + glue.prepareGlue(typeRegistry); + + String featurePath = "someFeature.feature"; + + checkAmbiguousCalled(featurePath); + //try again to verify if we don't cache when there is ambiguous step + checkAmbiguousCalled(featurePath); + } + + private void checkAmbiguousCalled(String featurePath) { + boolean ambiguousCalled = false; + try { + + glue.stepDefinitionMatch(featurePath, getPickleStep("pattern1")); + } catch (AmbiguousStepDefinitionsException e) { + assertEquals(2, e.getMatches().size()); + ambiguousCalled = true; + } + assertTrue(ambiguousCalled); + } + + private static PickleStep getPickleStep(String text) { + return new PickleStep(text, Collections.emptyList(), Collections.emptyList()); + } + + private static class MockedScenarioScopedStepDefinition implements StepDefinition, ScenarioScoped { + + private final String pattern; + + MockedScenarioScopedStepDefinition(String pattern) { + this.pattern = pattern; + } + + MockedScenarioScopedStepDefinition() { + this("mocked scenario scoped step definition"); + } + + boolean disposed; + + @Override + public void disposeScenarioScope() { + this.disposed = true; + } + + @Override + public String getLocation(boolean detail) { + return "mocked scenario scoped step definition"; + } + + @Override + public void execute(Object[] args) { + + } + + @Override + public boolean isDefinedAt(StackTraceElement stackTraceElement) { + return false; + } + + @Override + public List parameterInfos() { + return null; + } + + @Override + public String getPattern() { + return pattern; + } + + } + + private static class MockedDataTableTypeDefinition implements DataTableTypeDefinition, ScenarioScoped { + + boolean disposed; + + @Override + public DataTableType dataTableType() { + return new DataTableType(Object.class, (DataTable table) -> new Object()); + } + + @Override + public String getLocation(boolean detail) { + return "mocked data table type definition"; + } + + @Override + public void disposeScenarioScope() { + this.disposed = true; + } + + } + + private static class MockedParameterTypeDefinition implements ParameterTypeDefinition, ScenarioScoped { + + boolean disposed; + + @Override + public void disposeScenarioScope() { + this.disposed = true; + } + + @Override + public ParameterType parameterType() { + return new ParameterType<>("mock", "[ab]", Object.class, (String arg) -> new Object()); + } + + @Override + public String getLocation(boolean detail) { + return "mocked parameter type location"; + } + } + + + private static class MockedScenarioScopedHookDefinition implements HookDefinition, ScenarioScoped { + + boolean disposed; + + @Override + public void disposeScenarioScope() { + this.disposed = true; + } + + @Override + public String getLocation(boolean detail) { + return "mocked scenario scoped hook definition"; + } + + @Override + public void execute(Scenario scenario) { + + } + + @Override + public boolean matches(Collection tags) { + return true; + } + + @Override + public int getOrder() { + return 0; + } + } + + private static class MockedStepDefinition implements StepDefinition { + + private final String pattern; + + MockedStepDefinition(String pattern) { + this.pattern = pattern; + } + + @Override + public String getLocation(boolean detail) { + return "mocked step location"; + } + + @Override + public void execute(Object[] args) { + + } + + @Override + public boolean isDefinedAt(StackTraceElement stackTraceElement) { + return false; + } + + @Override + public List parameterInfos() { + return null; + } + + @Override + public String getPattern() { + return pattern; + } + } + + private static class MockedDefaultParameterTransformer implements DefaultParameterTransformerDefinition, ScenarioScoped { + + boolean disposed; + + @Override + public void disposeScenarioScope() { + this.disposed = true; + } + + @Override + public ParameterByTypeTransformer parameterByTypeTransformer() { + return (fromValue, toValueType) -> new Object(); + } + + @Override + public String getLocation(boolean detail) { + return "mocked default parameter transformer"; + } + } + + private static class MockedDefaultDataTableCellTransformer implements DefaultDataTableCellTransformerDefinition, ScenarioScoped { + + + boolean disposed; + + @Override + public void disposeScenarioScope() { + this.disposed = true; + } + + @Override + public TableCellByTypeTransformer tableCellByTypeTransformer() { + return new TableCellByTypeTransformer() { + @Override + public T transform(String value, Class cellType) { + return (T) new Object(); + } + }; + } + + @Override + public String getLocation(boolean detail) { + return "mocked default data table cell transformer"; + } + } + + private static class MockedDefaultDataTableEntryTransformer implements DefaultDataTableEntryTransformerDefinition, ScenarioScoped { + boolean disposed; + + @Override + public void disposeScenarioScope() { + this.disposed = true; + } + + @Override + public TableEntryByTypeTransformer tableEntryByTypeTransformer() { + return new TableEntryByTypeTransformer() { + @Override + public T transform(Map entry, Class type, TableCellByTypeTransformer cellTransformer) { + return (T) new Object(); + } + }; + } + + @Override + public String getLocation(boolean detail) { + return "mocked default data table entry transformer"; + } + } +} diff --git a/core/src/test/java/io/cucumber/core/runner/ClockStub.java b/core/src/test/java/io/cucumber/core/runner/ClockStub.java new file mode 100644 index 0000000000..57c65753ff --- /dev/null +++ b/core/src/test/java/io/cucumber/core/runner/ClockStub.java @@ -0,0 +1,33 @@ +package io.cucumber.core.runner; + +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneId; + +public class ClockStub extends Clock { + private final Duration duration; + private final ThreadLocal currentInstant = new ThreadLocal<>(); + + public ClockStub(Duration duration) { + this.duration = duration; + } + + @Override + public ZoneId getZone() { + return null; + } + + @Override + public Clock withZone(ZoneId zone) { + return null; + } + + @Override + public Instant instant() { + Instant result = currentInstant.get(); + result = result != null ? result : Instant.EPOCH; + currentInstant.set(result.plus(duration)); + return result; + } +} diff --git a/java/src/test/java/cucumber/runtime/java/JavaStepDefinitionTransposeTest.java b/core/src/test/java/io/cucumber/core/runner/CoreStepDefinitionTest.java old mode 100755 new mode 100644 similarity index 53% rename from java/src/test/java/cucumber/runtime/java/JavaStepDefinitionTransposeTest.java rename to core/src/test/java/io/cucumber/core/runner/CoreStepDefinitionTest.java index e2c8772a52..ae9244824b --- a/java/src/test/java/cucumber/runtime/java/JavaStepDefinitionTransposeTest.java +++ b/core/src/test/java/io/cucumber/core/runner/CoreStepDefinitionTest.java @@ -1,16 +1,14 @@ -package cucumber.runtime.java; +package io.cucumber.core.runner; -import cucumber.api.Transpose; -import cucumber.api.java.ObjectFactory; -import cucumber.runtime.StepDefinition; import gherkin.pickles.PickleCell; import gherkin.pickles.PickleLocation; import gherkin.pickles.PickleRow; import gherkin.pickles.PickleStep; +import gherkin.pickles.PickleString; import gherkin.pickles.PickleTable; +import io.cucumber.core.stepexpression.Argument; +import io.cucumber.core.stepexpression.TypeRegistry; import io.cucumber.datatable.DataTable; -import io.cucumber.stepexpression.Argument; -import io.cucumber.stepexpression.TypeRegistry; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -22,121 +20,117 @@ import java.util.Map; import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.hamcrest.collection.IsMapContaining.hasEntry; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; -public class JavaStepDefinitionTransposeTest { +public class CoreStepDefinitionTest { + @Rule public ExpectedException thrown = ExpectedException.none(); private final TypeRegistry typeRegistry = new TypeRegistry(Locale.ENGLISH); - public static class StepDefs { - List> listOfListOfDoubles; - public Map mapOfDoubleToDouble; - - public DataTable dataTable; - private Map> mapOfDoubleToListOfDouble; + @Test + public void should_apply_identity_transform_to_doc_string_when_target_type_is_object() { + StubStepDefinition stub = new StubStepDefinition("I have some step", Object.class); + CoreStepDefinition stepDefinition = new CoreStepDefinition(stub, typeRegistry); + PickleString pickleString = new PickleString(null, "content", "text"); + List arguments = stepDefinition.matchedArguments(new PickleStep("I have some step", singletonList(pickleString), emptyList())); + assertEquals("content", arguments.get(0).getValue()); + } - public void listOfListOfDoubles(List> listOfListOfDoubles) { - this.listOfListOfDoubles = listOfListOfDoubles; - } + @Test + public void should_apply_identity_transform_to_data_table_when_target_type_is_object() { + StubStepDefinition stub = new StubStepDefinition("I have some step", Object.class); + CoreStepDefinition stepDefinition = new CoreStepDefinition(stub, typeRegistry); - public void listOfListOfDoublesTransposed(@Transpose List> listOfListOfDoubles) { - this.listOfListOfDoubles = listOfListOfDoubles; - } + PickleTable table = new PickleTable(singletonList(new PickleRow(singletonList(new PickleCell(null, "content"))))); + List arguments = stepDefinition.matchedArguments(new PickleStep("I have some step", singletonList(table), emptyList())); + assertEquals(DataTable.create(singletonList(singletonList("content"))), arguments.get(0).getValue()); + } - public void plainDataTable(DataTable dataTable) { - this.dataTable = dataTable; - } - public void transposedDataTable(@Transpose DataTable dataTable) { - this.dataTable = dataTable; + public static class StepDefs { + public void listOfListOfDoubles(List> listOfListOfDoubles) { } - public void newtransposedAnnotationDataTable(@io.cucumber.java.Transpose DataTable dataTable) { - this.dataTable = dataTable; + public void plainDataTable(DataTable dataTable) { } public void mapOfDoubleToDouble(Map mapOfDoubleToDouble) { - this.mapOfDoubleToDouble = mapOfDoubleToDouble; } - public void transposedMapOfDoubleToListOfDouble(@Transpose Map> mapOfDoubleToListOfDouble) { - this.mapOfDoubleToListOfDouble = mapOfDoubleToListOfDouble; + public void transposedMapOfDoubleToListOfDouble(Map> mapOfDoubleToListOfDouble) { } + } @Test public void transforms_to_map_of_double_to_double() throws Throwable { Method m = StepDefs.class.getMethod("mapOfDoubleToDouble", Map.class); - 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)); + Map stepDefs = runStepDef(m, false, new PickleTable(listOfDoublesWithoutHeader())); + + assertThat(stepDefs, hasEntry(1000.0, 999.0)); + assertThat(stepDefs, hasEntry(0.5, -0.5)); + assertThat(stepDefs, hasEntry(100.5, 99.5)); } @Test public void transforms_transposed_to_map_of_double_to_double() throws Throwable { Method m = StepDefs.class.getMethod("transposedMapOfDoubleToListOfDouble", Map.class); - StepDefs stepDefs = runStepDef(m, new PickleTable(listOfDoublesWithoutHeader())); - assertEquals(asList(0.5, 1000.0), stepDefs.mapOfDoubleToListOfDouble.get(100.5)); + Map> stepDefs = runStepDef(m, true, new PickleTable(listOfDoublesWithoutHeader())); + assertThat(stepDefs, hasEntry(100.5, asList(0.5, 1000.0))); } @Test public void transforms_to_list_of_single_values() throws Throwable { Method m = StepDefs.class.getMethod("listOfListOfDoubles", List.class); - StepDefs stepDefs = runStepDef(m, new PickleTable(listOfDoublesWithoutHeader())); - assertEquals("[[100.5, 99.5], [0.5, -0.5], [1000.0, 999.0]]", stepDefs.listOfListOfDoubles.toString()); + List> stepDefs = runStepDef(m, false, new PickleTable(listOfDoublesWithoutHeader())); + assertEquals("[[100.5, 99.5], [0.5, -0.5], [1000.0, 999.0]]", stepDefs.toString()); } @Test public void transforms_to_list_of_single_values_transposed() throws Throwable { - Method m = StepDefs.class.getMethod("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()); + Method m = StepDefs.class.getMethod("listOfListOfDoubles", List.class); + List> stepDefs = runStepDef(m, true, new PickleTable(transposedListOfDoublesWithoutHeader())); + assertEquals("[[100.5, 99.5], [0.5, -0.5], [1000.0, 999.0]]", stepDefs.toString()); } @Test public void passes_plain_data_table() throws Throwable { Method m = StepDefs.class.getMethod("plainDataTable", DataTable.class); - StepDefs stepDefs = runStepDef(m, new PickleTable(listOfDatesWithHeader())); - assertEquals("Birth Date", stepDefs.dataTable.cell(0, 0)); - assertEquals("1957-05-10", stepDefs.dataTable.cell(1, 0)); + DataTable stepDefs = runStepDef(m, false, new PickleTable(listOfDatesWithHeader())); + assertEquals("Birth Date", stepDefs.cell(0, 0)); + assertEquals("1957-05-10", stepDefs.cell(1, 0)); } @Test public void passes_transposed_data_table() throws Throwable { - Method m = StepDefs.class.getMethod("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)); - } - - @Test - public void passes_newtransposedAnnotation_data_table() throws Throwable { - Method m = StepDefs.class.getMethod("newtransposedAnnotationDataTable", 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)); + Method m = StepDefs.class.getMethod("plainDataTable", DataTable.class); + DataTable stepDefs = runStepDef(m, true, new PickleTable(listOfDatesWithHeader())); + assertEquals("Birth Date", stepDefs.cell(0, 0)); + assertEquals("1957-05-10", stepDefs.cell(0, 1)); } - private StepDefs runStepDef(Method method, PickleTable table) throws Throwable { - StepDefs stepDefs = new StepDefs(); - ObjectFactory objectFactory = new SingletonFactory(stepDefs); + private T runStepDef(Method method, boolean transposed, PickleTable table) throws Throwable { + StubStepDefinition stub = new StubStepDefinition("some text", transposed, method.getGenericParameterTypes()); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stub, typeRegistry); - 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); + List arguments = coreStepDefinition.matchedArguments(stepWithTable); List result = new ArrayList<>(); for (Argument argument : arguments) { result.add(argument.getValue()); } - stepDefinition.execute(result.toArray(new Object[0])); + coreStepDefinition.getStepDefinition().execute(result.toArray(new Object[0])); - return stepDefs; + return (T) stub.getArgs().get(0); } private List listOfDatesWithHeader() { @@ -161,4 +155,4 @@ private List transposedListOfDoublesWithoutHeader() { return rows; } -} +} \ No newline at end of file diff --git a/core/src/test/java/cucumber/runner/EventBusTest.java b/core/src/test/java/io/cucumber/core/runner/EventBusTest.java similarity index 50% rename from core/src/test/java/cucumber/runner/EventBusTest.java rename to core/src/test/java/io/cucumber/core/runner/EventBusTest.java index eea7c75d8c..ba535a69d8 100644 --- a/core/src/test/java/cucumber/runner/EventBusTest.java +++ b/core/src/test/java/io/cucumber/core/runner/EventBusTest.java @@ -1,28 +1,38 @@ -package cucumber.runner; - -import cucumber.api.PickleStepTestStep; -import cucumber.api.Result; -import cucumber.api.TestCase; -import cucumber.api.event.EventHandler; -import cucumber.api.event.TestStepFinished; -import cucumber.api.event.TestStepStarted; -import org.junit.Test; +package io.cucumber.core.runner; +import static java.time.Duration.ZERO; +import static java.time.Instant.EPOCH; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; + +import io.cucumber.core.event.Result; +import io.cucumber.core.event.Status; +import io.cucumber.core.runtime.TimeServiceEventBus; +import org.junit.Test; + +import io.cucumber.core.event.EventHandler; +import io.cucumber.core.event.PickleStepTestStep; +import io.cucumber.core.event.TestCase; +import io.cucumber.core.event.TestStepFinished; +import io.cucumber.core.event.TestStepStarted; +import io.cucumber.core.eventbus.EventBus; + public class EventBusTest { @Test public void handlers_receive_the_events_they_registered_for() { EventHandler handler = mock(EventHandler.class); PickleStepTestStep testStep = mock(PickleStepTestStep.class); - Result result = new Result(Result.Type.PASSED, 0L, null); + Result result = new Result(Status.PASSED, ZERO, null); TestCase testCase = mock(TestCase.class); - TestStepFinished event = new TestStepFinished(0L, 0L, testCase, testStep, result); + TestStepFinished event = new TestStepFinished(EPOCH, testCase, testStep, result); - EventBus bus = new TimeServiceEventBus(new TimeServiceStub(0)); + EventBus bus = new TimeServiceEventBus(Clock.fixed(Instant.EPOCH, ZoneId.of("UTC"))); bus.registerHandlerFor(TestStepFinished.class, handler); bus.send(event); @@ -34,9 +44,9 @@ public void handlers_do_not_receive_the_events_they_did_not_registered_for() { EventHandler handler = mock(EventHandler.class); PickleStepTestStep testStep = mock(PickleStepTestStep.class); TestCase testCase = mock(TestCase.class); - TestStepStarted event = new TestStepStarted(0L, 0L, testCase, testStep); + TestStepStarted event = new TestStepStarted(EPOCH, testCase, testStep); - EventBus bus = new TimeServiceEventBus(new TimeServiceStub(0)); + EventBus bus = new TimeServiceEventBus(Clock.fixed(Instant.EPOCH, ZoneId.of("UTC"))); bus.registerHandlerFor(TestStepFinished.class, handler); bus.send(event); diff --git a/core/src/test/java/cucumber/runner/FailedStepInstantiationMatchTest.java b/core/src/test/java/io/cucumber/core/runner/FailedStepInstantiationMatchTest.java similarity index 58% rename from core/src/test/java/cucumber/runner/FailedStepInstantiationMatchTest.java rename to core/src/test/java/io/cucumber/core/runner/FailedStepInstantiationMatchTest.java index 5926ae2641..c2d7532639 100644 --- a/core/src/test/java/cucumber/runner/FailedStepInstantiationMatchTest.java +++ b/core/src/test/java/io/cucumber/core/runner/FailedStepInstantiationMatchTest.java @@ -1,16 +1,22 @@ -package cucumber.runner; +package io.cucumber.core.runner; -import cucumber.api.Scenario; +import io.cucumber.core.api.Scenario; import gherkin.pickles.PickleLocation; import gherkin.pickles.PickleStep; import org.junit.Before; import org.junit.Test; +import org.junit.jupiter.api.function.Executable; import static java.util.Arrays.asList; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNull.nullValue; +import static org.junit.Assert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class FailedStepInstantiationMatchTest { + private FailedPickleStepInstantiationMatch match; @Before @@ -26,13 +32,18 @@ public void create_match() { match = new FailedPickleStepInstantiationMatch("uri", step, exception); } - @Test(expected=Exception.class) - public void throws_the_exception_passed_to_the_match_when_run() throws Throwable { - match.runStep(mock(Scenario.class)); + @Test + public void throws_the_exception_passed_to_the_match_when_run() { + final Executable testMethod = () -> match.runStep(mock(Scenario.class)); + final Exception expectedThrown = assertThrows(Exception.class, testMethod); + assertThat(expectedThrown.getMessage(), is(nullValue())); } - @Test(expected=Exception.class) - public void throws_the_exception_passed_to_the_match_when_dry_run() throws Throwable { - match.dryRunStep(mock(Scenario.class)); + @Test + public void throws_the_exception_passed_to_the_match_when_dry_run() { + final Executable testMethod = () -> match.dryRunStep(mock(Scenario.class)); + final Exception expectedThrown = assertThrows(Exception.class, testMethod); + assertThat(expectedThrown.getMessage(), is(nullValue())); } + } diff --git a/core/src/test/java/cucumber/runner/HookOrderTest.java b/core/src/test/java/io/cucumber/core/runner/HookOrderTest.java similarity index 59% rename from core/src/test/java/cucumber/runner/HookOrderTest.java rename to core/src/test/java/io/cucumber/core/runner/HookOrderTest.java index 09c119aa2d..68ac48a788 100644 --- a/core/src/test/java/cucumber/runner/HookOrderTest.java +++ b/core/src/test/java/io/cucumber/core/runner/HookOrderTest.java @@ -1,43 +1,43 @@ -package cucumber.runner; +package io.cucumber.core.runner; -import cucumber.api.Scenario; -import io.cucumber.core.options.RuntimeOptions; -import cucumber.runtime.Glue; -import cucumber.runtime.HookDefinition; -import cucumber.runtime.StubStepDefinition; import gherkin.events.PickleEvent; import gherkin.pickles.Argument; import gherkin.pickles.Pickle; import gherkin.pickles.PickleLocation; import gherkin.pickles.PickleStep; import gherkin.pickles.PickleTag; -import io.cucumber.stepexpression.TypeRegistry; +import io.cucumber.core.api.Scenario; +import io.cucumber.core.backend.Glue; +import io.cucumber.core.backend.HookDefinition; +import io.cucumber.core.eventbus.EventBus; +import io.cucumber.core.options.RuntimeOptions; +import io.cucumber.core.runtime.StubStepDefinition; +import io.cucumber.core.runtime.TimeServiceEventBus; import org.junit.Test; import org.mockito.ArgumentMatchers; import org.mockito.InOrder; import java.net.URI; +import java.time.Clock; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Locale; import static java.util.Collections.singletonList; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class HookOrderTest { private final static String ENGLISH = "en"; private final RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); - private final EventBus bus = new TimeServiceEventBus(TimeService.SYSTEM); + private final EventBus bus = new TimeServiceEventBus(Clock.systemUTC()); - private final StubStepDefinition stepDefinition = new StubStepDefinition("pattern1", new TypeRegistry(Locale.ENGLISH)); - private final PickleStep pickleStep = new PickleStep("pattern1", Collections.emptyList(), singletonList(new PickleLocation(2,2))); + private final StubStepDefinition stepDefinition = new StubStepDefinition("pattern1"); + private final PickleStep pickleStep = new PickleStep("pattern1", Collections.emptyList(), singletonList(new PickleLocation(2,2))); private final PickleEvent pickleEvent = new PickleEvent("uri", - new Pickle("scenario1", ENGLISH, singletonList(pickleStep), Collections.emptyList(), singletonList(new PickleLocation(1,1)))); + new Pickle("scenario1", ENGLISH, singletonList(pickleStep), Collections.emptyList(), singletonList(new PickleLocation(1,1)))); @Test public void before_hooks_execute_in_order() throws Throwable { @@ -45,8 +45,8 @@ public void before_hooks_execute_in_order() throws Throwable { TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override - public void loadGlue(cucumber.runtime.Glue glue, List gluePaths) { - glue.addStepDefinition(new StubStepDefinition("pattern1", new TypeRegistry(Locale.ENGLISH))); + public void loadGlue(Glue glue, List gluePaths) { + glue.addStepDefinition(new StubStepDefinition("pattern1")); for (HookDefinition hook : hooks) { glue.addBeforeHook(hook); } @@ -57,13 +57,13 @@ public void loadGlue(cucumber.runtime.Glue glue, List gluePaths) { runnerSupplier.get().runPickle(pickleEvent); InOrder inOrder = inOrder(hooks.toArray()); - inOrder.verify(hooks.get(6)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(3)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(4)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(2)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(0)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(5)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(1)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(6)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(3)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(4)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(2)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(0)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(5)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(1)).execute(ArgumentMatchers.any()); } @Test @@ -72,7 +72,7 @@ public void before_step_hooks_execute_in_order() throws Throwable { TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override - public void loadGlue(cucumber.runtime.Glue glue, List gluePaths) { + public void loadGlue(Glue glue, List gluePaths) { glue.addStepDefinition(stepDefinition); for (HookDefinition hook : hooks) { glue.addBeforeStepHook(hook); @@ -84,13 +84,13 @@ public void loadGlue(cucumber.runtime.Glue glue, List gluePaths) { runnerSupplier.get().runPickle(pickleEvent); InOrder inOrder = inOrder(hooks.toArray()); - inOrder.verify(hooks.get(6)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(3)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(4)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(2)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(0)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(5)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(1)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(6)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(3)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(4)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(2)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(0)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(5)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(1)).execute(ArgumentMatchers.any()); } @Test @@ -99,7 +99,7 @@ public void after_hooks_execute_in_reverse_order() throws Throwable { TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override - public void loadGlue(cucumber.runtime.Glue glue, List gluePaths) { + public void loadGlue(Glue glue, List gluePaths) { glue.addStepDefinition(stepDefinition); for (HookDefinition hook : hooks) { glue.addAfterHook(hook); @@ -111,13 +111,13 @@ public void loadGlue(cucumber.runtime.Glue glue, List gluePaths) { runnerSupplier.get().runPickle(pickleEvent); InOrder inOrder = inOrder(hooks.toArray()); - inOrder.verify(hooks.get(2)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(6)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(3)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(1)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(5)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(4)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(0)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(2)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(6)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(3)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(1)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(5)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(4)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(0)).execute(ArgumentMatchers.any()); } @Test @@ -126,7 +126,7 @@ public void after_step_hooks_execute_in_reverse_order() throws Throwable { TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override - public void loadGlue(cucumber.runtime.Glue glue, List gluePaths) { + public void loadGlue(Glue glue, List gluePaths) { glue.addStepDefinition(stepDefinition); for (HookDefinition hook : hooks) { glue.addAfterStepHook(hook); @@ -138,13 +138,13 @@ public void loadGlue(cucumber.runtime.Glue glue, List gluePaths) { runnerSupplier.get().runPickle(pickleEvent); InOrder inOrder = inOrder(hooks.toArray()); - inOrder.verify(hooks.get(2)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(6)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(3)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(1)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(5)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(4)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(0)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(2)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(6)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(3)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(1)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(5)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(4)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(0)).execute(ArgumentMatchers.any()); } @Test @@ -175,12 +175,12 @@ public void loadGlue(Glue glue, List gluePaths) { allHooks.addAll(backend2Hooks); InOrder inOrder = inOrder(allHooks.toArray()); - inOrder.verify(backend1Hooks.get(2)).execute(ArgumentMatchers.any()); - inOrder.verify(backend2Hooks.get(0)).execute(ArgumentMatchers.any()); - inOrder.verify(backend1Hooks.get(0)).execute(ArgumentMatchers.any()); - inOrder.verify(backend2Hooks.get(2)).execute(ArgumentMatchers.any()); - verify(backend2Hooks.get(1)).execute(ArgumentMatchers.any()); - verify(backend1Hooks.get(1)).execute(ArgumentMatchers.any()); + inOrder.verify(backend1Hooks.get(2)).execute(ArgumentMatchers.any()); + inOrder.verify(backend2Hooks.get(0)).execute(ArgumentMatchers.any()); + inOrder.verify(backend1Hooks.get(0)).execute(ArgumentMatchers.any()); + inOrder.verify(backend2Hooks.get(2)).execute(ArgumentMatchers.any()); + inOrder.verify(backend1Hooks.get(1)).execute(ArgumentMatchers.any()); + inOrder.verify(backend2Hooks.get(1)).execute(ArgumentMatchers.any()); } private List mockHooks(int... ordering) { @@ -188,7 +188,7 @@ private List mockHooks(int... ordering) { for (int order : ordering) { HookDefinition hook = mock(HookDefinition.class, "Mock number " + order); when(hook.getOrder()).thenReturn(order); - when(hook.matches(ArgumentMatchers.anyList())).thenReturn(true); + when(hook.matches(ArgumentMatchers.anyList())).thenReturn(true); hooks.add(hook); } return hooks; diff --git a/core/src/test/java/cucumber/runner/HookTest.java b/core/src/test/java/io/cucumber/core/runner/HookTest.java similarity index 59% rename from core/src/test/java/cucumber/runner/HookTest.java rename to core/src/test/java/io/cucumber/core/runner/HookTest.java index 3e10c7bca8..8fef4acf87 100644 --- a/core/src/test/java/cucumber/runner/HookTest.java +++ b/core/src/test/java/io/cucumber/core/runner/HookTest.java @@ -1,23 +1,24 @@ -package cucumber.runner; +package io.cucumber.core.runner; -import cucumber.api.Scenario; -import cucumber.runtime.Backend; -import io.cucumber.core.options.RuntimeOptions; -import cucumber.runtime.Glue; -import cucumber.runtime.HookDefinition; import gherkin.events.PickleEvent; -import gherkin.pickles.Argument; import gherkin.pickles.Pickle; import gherkin.pickles.PickleLocation; import gherkin.pickles.PickleStep; -import gherkin.pickles.PickleTag; +import io.cucumber.core.api.TypeRegistryConfigurer; +import io.cucumber.core.backend.Backend; +import io.cucumber.core.backend.Glue; +import io.cucumber.core.backend.HookDefinition; +import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.eventbus.EventBus; +import io.cucumber.core.options.RuntimeOptions; +import io.cucumber.core.runtime.TimeServiceEventBus; import org.junit.Test; import org.mockito.ArgumentMatchers; import org.mockito.InOrder; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -import java.net.URI; +import java.time.Clock; import java.util.Collections; import static java.util.Collections.singletonList; @@ -29,38 +30,36 @@ public class HookTest { private final static String ENGLISH = "en"; - private final EventBus bus = new TimeServiceEventBus(TimeService.SYSTEM); + private final EventBus bus = new TimeServiceEventBus(Clock.systemUTC()); private final RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); - private final PickleStep pickleStep = new PickleStep("pattern1", Collections.emptyList(), singletonList(new PickleLocation(2, 2))); + private final PickleStep pickleStep = new PickleStep("pattern1", Collections.emptyList(), singletonList(new PickleLocation(2, 2))); private final PickleEvent pickleEvent = new PickleEvent("uri", - new Pickle("scenario1", ENGLISH, singletonList(pickleStep), Collections.emptyList(), singletonList(new PickleLocation(1, 1)))); + new Pickle("scenario1", ENGLISH, singletonList(pickleStep), Collections.emptyList(), singletonList(new PickleLocation(1, 1)))); /** * Test for #23. */ @Test public void after_hooks_execute_before_objects_are_disposed() throws Throwable { - Backend backend = mock(Backend.class); + ObjectFactory objectFactory = mock(ObjectFactory.class); final HookDefinition hook = mock(HookDefinition.class); - when(hook.matches(ArgumentMatchers.anyCollection())).thenReturn(true); + TypeRegistryConfigurer typeRegistryConfigurer = mock(TypeRegistryConfigurer.class); + when(hook.matches(ArgumentMatchers.anyCollection())).thenReturn(true); - doAnswer(new Answer() { - @Override - public Object answer(InvocationOnMock invocation) { - cucumber.runtime.Glue glue = invocation.getArgument(0); - glue.addBeforeHook(hook); - return null; - } - }).when(backend).loadGlue(any(Glue.class), ArgumentMatchers.anyList()); + doAnswer(invocation -> { + Glue glue = invocation.getArgument(0); + glue.addBeforeHook(hook); + return null; + }).when(backend).loadGlue(any(Glue.class), ArgumentMatchers.anyList()); - Runner runner = new Runner(bus, Collections.singleton(backend), runtimeOptions); + Runner runner = new Runner(bus, Collections.singleton(backend), objectFactory, typeRegistryConfigurer, runtimeOptions); runner.runPickle(pickleEvent); InOrder inOrder = inOrder(hook, backend); inOrder.verify(backend).buildWorld(); - inOrder.verify(hook).execute(ArgumentMatchers.any()); + inOrder.verify(hook).execute(ArgumentMatchers.any()); inOrder.verify(backend).disposeWorld(); } } diff --git a/core/src/test/java/cucumber/runner/HookTestStepTest.java b/core/src/test/java/io/cucumber/core/runner/HookTestStepTest.java similarity index 75% rename from core/src/test/java/cucumber/runner/HookTestStepTest.java rename to core/src/test/java/io/cucumber/core/runner/HookTestStepTest.java index e04615fb2d..2b9dcf6322 100644 --- a/core/src/test/java/cucumber/runner/HookTestStepTest.java +++ b/core/src/test/java/io/cucumber/core/runner/HookTestStepTest.java @@ -1,15 +1,19 @@ -package cucumber.runner; +package io.cucumber.core.runner; -import cucumber.api.HookType; -import cucumber.api.Result; -import cucumber.api.event.TestStepFinished; -import cucumber.api.event.TestStepStarted; -import cucumber.runner.EventBus; -import cucumber.runtime.HookDefinition; +import io.cucumber.core.event.HookType; +import io.cucumber.core.event.Status; +import io.cucumber.core.event.TestStepFinished; +import io.cucumber.core.event.TestStepStarted; +import io.cucumber.core.backend.HookDefinition; import gherkin.events.PickleEvent; +import io.cucumber.core.eventbus.EventBus; + +import org.junit.Before; import org.junit.Test; import org.mockito.InOrder; +import org.mockito.Mockito; +import java.time.Instant; import java.util.Collections; import static org.junit.Assert.assertEquals; @@ -32,8 +36,13 @@ public class HookTestStepTest { ); private final EventBus bus = mock(EventBus.class); private final Scenario scenario = new Scenario(bus, testCase); - private HookTestStep step = new HookTestStep(HookType.AfterStep, definitionMatch); + private HookTestStep step = new HookTestStep(HookType.AFTER_STEP, definitionMatch); + @Before + public void init() { + Mockito.when(bus.getInstant()).thenReturn(Instant.now()); + } + @Test public void run_does_run() throws Throwable { step.run(testCase, bus, scenario, false); @@ -58,13 +67,13 @@ public void run_does_dry_run() throws Throwable { public void result_is_passed_when_step_definition_does_not_throw_exception() { boolean skipNextStep = step.run(testCase, bus, scenario, false); assertFalse(skipNextStep); - assertEquals(Result.Type.PASSED, scenario.getStatus()); + assertEquals(Status.PASSED, scenario.getStatus()); } @Test public void result_is_skipped_when_skip_step_is_skip_all_skipable() { boolean skipNextStep = step.run(testCase, bus, scenario, true); assertTrue(skipNextStep); - assertEquals(Result.Type.SKIPPED, scenario.getStatus()); + assertEquals(Status.SKIPPED, scenario.getStatus()); } } diff --git a/core/src/test/java/cucumber/runner/PickleStepTestStepTest.java b/core/src/test/java/io/cucumber/core/runner/PickleStepTestStepTest.java similarity index 79% rename from core/src/test/java/cucumber/runner/PickleStepTestStepTest.java rename to core/src/test/java/io/cucumber/core/runner/PickleStepTestStepTest.java index 1a898fa38e..5b4de3fd3f 100644 --- a/core/src/test/java/cucumber/runner/PickleStepTestStepTest.java +++ b/core/src/test/java/io/cucumber/core/runner/PickleStepTestStepTest.java @@ -1,27 +1,34 @@ -package cucumber.runner; - -import cucumber.api.PendingException; -import cucumber.api.Result; -import cucumber.api.event.TestCaseEvent; -import cucumber.api.event.TestStepFinished; -import cucumber.api.event.TestStepStarted; -import cucumber.runtime.HookDefinition; +package io.cucumber.core.runner; + +import io.cucumber.core.event.Result; +import io.cucumber.core.event.Status; +import io.cucumber.core.event.TestCaseEvent; +import io.cucumber.core.event.TestStepFinished; +import io.cucumber.core.event.TestStepStarted; +import io.cucumber.core.backend.HookDefinition; import gherkin.events.PickleEvent; import gherkin.pickles.PickleStep; +import io.cucumber.core.eventbus.EventBus; import org.junit.AssumptionViolatedException; +import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; import org.mockito.InOrder; +import org.mockito.Mockito; +import java.time.Instant; import java.util.Collections; import java.util.List; -import static cucumber.api.HookType.AfterStep; -import static cucumber.api.HookType.BeforeStep; -import static cucumber.api.Result.Type.FAILED; -import static cucumber.api.Result.Type.PASSED; -import static cucumber.api.Result.Type.SKIPPED; +import static io.cucumber.core.event.HookType.AFTER_STEP; +import static io.cucumber.core.event.HookType.BEFORE_STEP; +import static io.cucumber.core.event.Status.FAILED; +import static io.cucumber.core.event.Status.PASSED; +import static io.cucumber.core.event.Status.SKIPPED; +import static java.time.Duration.ZERO; +import static java.time.Duration.ofMillis; +import static java.time.Instant.ofEpochMilli; import static java.util.Collections.singletonList; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; @@ -47,8 +54,8 @@ public class PickleStepTestStepTest { private final PickleStepDefinitionMatch definitionMatch = mock(PickleStepDefinitionMatch.class); private HookDefinition afterHookDefinition = mock(HookDefinition.class); private HookDefinition beforeHookDefinition = mock(HookDefinition.class); - private final HookTestStep beforeHook = new HookTestStep(BeforeStep, new HookDefinitionMatch(beforeHookDefinition)); - private final HookTestStep afterHook = new HookTestStep(AfterStep, new HookDefinitionMatch(afterHookDefinition)); + private final HookTestStep beforeHook = new HookTestStep(BEFORE_STEP, new HookDefinitionMatch(beforeHookDefinition)); + private final HookTestStep afterHook = new HookTestStep(AFTER_STEP, new HookDefinitionMatch(afterHookDefinition)); private final PickleStepTestStep step = new PickleStepTestStep( "uri", mock(PickleStep.class), @@ -57,6 +64,11 @@ public class PickleStepTestStepTest { definitionMatch ); + @Before + public void init() { + Mockito.when(bus.getInstant()).thenReturn(Instant.now()); + } + @Test public void run_wraps_run_step_in_test_step_started_and_finished_events() throws Throwable { step.run(testCase, bus, scenario, false); @@ -94,7 +106,7 @@ public void result_is_skipped_when_skip_step_is_not_run_all() { @Test public void result_is_skipped_when_before_step_hook_does_not_pass() throws Throwable { - doThrow(AssumptionViolatedException.class).when(beforeHookDefinition).execute(any(cucumber.api.Scenario.class)); + doThrow(AssumptionViolatedException.class).when(beforeHookDefinition).execute(any(io.cucumber.core.api.Scenario.class)); boolean skipNextStep = step.run(testCase, bus, scenario, false); assertTrue(skipNextStep); assertEquals(SKIPPED, scenario.getStatus()); @@ -102,7 +114,7 @@ public void result_is_skipped_when_before_step_hook_does_not_pass() throws Throw @Test public void step_execution_is_dry_run_when_before_step_hook_does_not_pass() throws Throwable { - doThrow(AssumptionViolatedException.class).when(beforeHookDefinition).execute(any(cucumber.api.Scenario.class)); + doThrow(AssumptionViolatedException.class).when(beforeHookDefinition).execute(any(io.cucumber.core.api.Scenario.class)); step.run(testCase, bus, scenario, false); verify(definitionMatch).dryRunStep(any(Scenario.class)); } @@ -110,8 +122,8 @@ public void step_execution_is_dry_run_when_before_step_hook_does_not_pass() thro @Test public void result_is_result_from_hook_when_before_step_hook_does_not_pass() throws Throwable { Exception exception = new RuntimeException(); - doThrow(exception).when(beforeHookDefinition).execute(any(cucumber.api.Scenario.class)); - Result failure = new Result(Result.Type.FAILED, 0L, exception); + doThrow(exception).when(beforeHookDefinition).execute(any(io.cucumber.core.api.Scenario.class)); + Result failure = new Result(Status.FAILED, ZERO, exception); boolean skipNextStep = step.run(testCase, bus, scenario, false); assertTrue(skipNextStep); assertEquals(FAILED, scenario.getStatus()); @@ -119,13 +131,13 @@ public void result_is_result_from_hook_when_before_step_hook_does_not_pass() thr ArgumentCaptor captor = forClass(TestCaseEvent.class); verify(bus, times(6)).send(captor.capture()); List allValues = captor.getAllValues(); - assertEquals(failure, ((TestStepFinished) allValues.get(1)).result); + assertEquals(failure, ((TestStepFinished) allValues.get(1)).getResult()); } @Test public void result_is_result_from_step_when_step_hook_does_not_pass() throws Throwable { RuntimeException runtimeException = new RuntimeException(); - Result failure = new Result(Result.Type.FAILED, 0L, runtimeException); + Result failure = new Result(Status.FAILED, ZERO, runtimeException); doThrow(runtimeException).when(definitionMatch).runStep(any(Scenario.class)); boolean skipNextStep = step.run(testCase, bus, scenario, false); assertTrue(skipNextStep); @@ -134,14 +146,14 @@ public void result_is_result_from_step_when_step_hook_does_not_pass() throws Thr ArgumentCaptor captor = forClass(TestCaseEvent.class); verify(bus, times(6)).send(captor.capture()); List allValues = captor.getAllValues(); - assertEquals(failure, ((TestStepFinished) allValues.get(3)).result); + assertEquals(failure, ((TestStepFinished) allValues.get(3)).getResult()); } @Test public void result_is_result_from_hook_when_after_step_hook_does_not_pass() throws Throwable { Exception exception = new RuntimeException(); - Result failure = new Result(Result.Type.FAILED, 0L, exception); - doThrow(exception).when(afterHookDefinition).execute(any(cucumber.api.Scenario.class)); + Result failure = new Result(Status.FAILED, ZERO, exception); + doThrow(exception).when(afterHookDefinition).execute(any(io.cucumber.core.api.Scenario.class)); boolean skipNextStep = step.run(testCase, bus, scenario, false); assertTrue(skipNextStep); assertEquals(FAILED, scenario.getStatus()); @@ -149,21 +161,21 @@ public void result_is_result_from_hook_when_after_step_hook_does_not_pass() thro ArgumentCaptor captor = forClass(TestCaseEvent.class); verify(bus, times(6)).send(captor.capture()); List allValues = captor.getAllValues(); - assertEquals(failure, ((TestStepFinished) allValues.get(5)).result); + assertEquals(failure, ((TestStepFinished) allValues.get(5)).getResult()); } @Test public void after_step_hook_is_run_when_before_step_hook_does_not_pass() throws Throwable { - doThrow(RuntimeException.class).when(beforeHookDefinition).execute(any(cucumber.api.Scenario.class)); + doThrow(RuntimeException.class).when(beforeHookDefinition).execute(any(io.cucumber.core.api.Scenario.class)); step.run(testCase, bus, scenario, false); - verify(afterHookDefinition).execute(any(cucumber.api.Scenario.class)); + verify(afterHookDefinition).execute(any(io.cucumber.core.api.Scenario.class)); } @Test public void after_step_hook_is_run_when_step_does_not_pass() throws Throwable { doThrow(Exception.class).when(definitionMatch).runStep(any(Scenario.class)); step.run(testCase, bus, scenario, false); - verify(afterHookDefinition).execute(any(cucumber.api.Scenario.class)); + verify(afterHookDefinition).execute(any(io.cucumber.core.api.Scenario.class)); } @Test @@ -210,23 +222,23 @@ public void result_is_failed_when_step_definition_throws_exception() throws Thro boolean skipNextStep = step.run(testCase, bus, scenario, false); assertTrue(skipNextStep); - assertEquals(Result.Type.FAILED, scenario.getStatus()); + assertEquals(Status.FAILED, scenario.getStatus()); } @Test public void result_is_pending_when_step_definition_throws_pending_exception() throws Throwable { - doThrow(PendingException.class).when(definitionMatch).runStep(any(Scenario.class)); + doThrow(TestPendingException.class).when(definitionMatch).runStep(any(Scenario.class)); boolean skipNextStep = step.run(testCase, bus, scenario, false); assertTrue(skipNextStep); - assertEquals(Result.Type.PENDING, scenario.getStatus()); + assertEquals(Status.PENDING, scenario.getStatus()); } @Test public void step_execution_time_is_measured() { TestStep step = new PickleStepTestStep("uri", mock(PickleStep.class), definitionMatch); - when(bus.getTime()).thenReturn(234L, (Long) 1234L); + when(bus.getInstant()).thenReturn(ofEpochMilli(234L), ofEpochMilli(1234L)); step.run(testCase, bus, scenario, false); ArgumentCaptor captor = forClass(TestCaseEvent.class); @@ -236,9 +248,9 @@ public void step_execution_time_is_measured() { TestStepStarted started = (TestStepStarted) allValues.get(0); TestStepFinished finished = (TestStepFinished) allValues.get(1); - assertEquals((Long) 234L, started.getTimeStamp()); - assertEquals((Long) 1234L, finished.getTimeStamp()); - assertEquals((Long) 1000L, finished.result.getDuration()); + assertEquals(ofEpochMilli(234L), started.getInstant()); + assertEquals(ofEpochMilli(1234L), finished.getInstant()); + assertEquals(ofMillis(1000L), finished.getResult().getDuration()); } } diff --git a/core/src/test/java/io/cucumber/core/runner/ResultTest.java b/core/src/test/java/io/cucumber/core/runner/ResultTest.java new file mode 100644 index 0000000000..73d8219040 --- /dev/null +++ b/core/src/test/java/io/cucumber/core/runner/ResultTest.java @@ -0,0 +1,112 @@ +package io.cucumber.core.runner; + +import io.cucumber.core.event.Result; +import io.cucumber.core.event.Status; +import org.junit.Test; + +import java.util.Comparator; +import java.util.List; + +import static io.cucumber.core.event.Status.AMBIGUOUS; +import static io.cucumber.core.event.Status.FAILED; +import static io.cucumber.core.event.Status.PASSED; +import static io.cucumber.core.event.Status.PENDING; +import static io.cucumber.core.event.Status.SKIPPED; +import static io.cucumber.core.event.Status.UNDEFINED; +import static java.time.Duration.ZERO; +import static java.util.Arrays.asList; +import static java.util.Collections.sort; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +public class ResultTest { + + @Test + public void severity_from_low_to_high_is_passed_skipped_pending_undefined_ambiguous_failed() { + Result passed = new Result(PASSED, ZERO, null); + Result skipped = new Result(SKIPPED, ZERO, null); + Result pending = new Result(PENDING, ZERO, null); + Result ambiguous = new Result(AMBIGUOUS, ZERO, null); + Result undefined = new Result(UNDEFINED, ZERO, null); + Result failed = new Result(FAILED, ZERO, null); + + List results = asList(pending, passed, skipped, failed, ambiguous, undefined); + + sort(results, Comparator.comparing(Result::getStatus)); + + assertThat(results, equalTo(asList(passed, skipped, pending, undefined, ambiguous, failed))); + } + + @Test + public void passed_result_is_always_ok() { + Result passedResult = new Result(PASSED, ZERO, null); + + assertTrue(passedResult.getStatus().isOk(isStrict(false))); + assertTrue(passedResult.getStatus().isOk(isStrict(true))); + } + + @Test + public void skipped_result_is_always_ok() { + Result skippedResult = new Result(SKIPPED, ZERO, null); + + assertTrue(skippedResult.getStatus().isOk(isStrict(false))); + assertTrue(skippedResult.getStatus().isOk(isStrict(true))); + } + + @Test + public void failed_result_is_never_ok() { + Result failedResult = new Result(FAILED, ZERO, null); + + assertFalse(failedResult.getStatus().isOk(isStrict(false))); + assertFalse(failedResult.getStatus().isOk(isStrict(true))); + } + + @Test + public void undefined_result_is_only_ok_when_not_strict() { + Result undefinedResult = new Result(UNDEFINED, ZERO, null); + + assertTrue(undefinedResult.getStatus().isOk(isStrict(false))); + assertFalse(undefinedResult.getStatus().isOk(isStrict(true))); + } + + @Test + public void pending_result_is_only_ok_when_not_strict() { + Result pendingResult = new Result(PENDING, ZERO, null); + + assertTrue(pendingResult.getStatus().isOk(isStrict(false))); + assertFalse(pendingResult.getStatus().isOk(isStrict(true))); + } + + @Test + public void is_query_returns_true_for_the_status_of_the_result_object() { + int checkCount = 0; + for (Status status : Status.values()) { + Result result = new Result(status, ZERO, null); + + assertTrue(result.getStatus().is(result.getStatus())); + checkCount += 1; + } + assertTrue("No checks performed", checkCount > 0); + } + + @Test + public void is_query_returns_false_for_statuses_different_from_the_status_of_the_result_object() { + int checkCount = 0; + for (Status resultStatus : Status.values()) { + Result result = new Result(resultStatus, ZERO, null); + for (Status status : Status.values()) { + if (status != resultStatus) { + assertFalse(result.getStatus().is(status)); + checkCount += 1; + } + } + } + assertTrue("No checks performed", checkCount > 0); + } + + private boolean isStrict(boolean value) { + return value; + } +} diff --git a/core/src/test/java/cucumber/runner/RunnerTest.java b/core/src/test/java/io/cucumber/core/runner/RunnerTest.java similarity index 75% rename from core/src/test/java/cucumber/runner/RunnerTest.java rename to core/src/test/java/io/cucumber/core/runner/RunnerTest.java index 409ec4e751..465492bd04 100644 --- a/core/src/test/java/cucumber/runner/RunnerTest.java +++ b/core/src/test/java/io/cucumber/core/runner/RunnerTest.java @@ -1,19 +1,21 @@ -package cucumber.runner; +package io.cucumber.core.runner; -import cucumber.api.Scenario; -import cucumber.runtime.Backend; -import io.cucumber.core.options.RuntimeOptions; -import cucumber.runtime.Glue; -import cucumber.runtime.HookDefinition; -import io.cucumber.core.options.RuntimeOptionsBuilder; -import cucumber.runtime.StepDefinition; -import cucumber.runtime.snippets.FunctionNameGenerator; import gherkin.events.PickleEvent; import gherkin.pickles.Pickle; import gherkin.pickles.PickleLocation; import gherkin.pickles.PickleStep; import gherkin.pickles.PickleTag; -import io.cucumber.stepexpression.Argument; +import io.cucumber.core.api.Scenario; +import io.cucumber.core.api.TypeRegistryConfigurer; +import io.cucumber.core.backend.Backend; +import io.cucumber.core.backend.Glue; +import io.cucumber.core.backend.HookDefinition; +import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.eventbus.EventBus; +import io.cucumber.core.options.RuntimeOptions; +import io.cucumber.core.options.RuntimeOptionsBuilder; +import io.cucumber.core.runtime.TimeServiceEventBus; +import io.cucumber.core.snippets.TestSnippet; import org.junit.Test; import org.mockito.ArgumentMatchers; import org.mockito.InOrder; @@ -21,19 +23,22 @@ import org.mockito.stubbing.Answer; import java.net.URI; -import java.util.ArrayList; +import java.time.Clock; import java.util.Collections; import java.util.List; import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -44,10 +49,9 @@ public class RunnerTest { private static final List NO_TAGS = Collections.emptyList(); private static final List MOCK_LOCATIONS = asList(mock(PickleLocation.class)); - private final RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); - private final EventBus bus = new TimeServiceEventBus(TimeService.SYSTEM); - + private final EventBus bus = new TimeServiceEventBus(Clock.systemUTC()); + private final TypeRegistryConfigurer typeRegistryConfigurer = typeRegistry -> {}; @Test public void hooks_execute_when_world_exist() throws Throwable { @@ -55,6 +59,8 @@ public void hooks_execute_when_world_exist() throws Throwable { final HookDefinition afterHook = addAfterHook(); Backend backend = mock(Backend.class); + when(backend.getSnippet()).thenReturn(new TestSnippet()); + ObjectFactory objectFactory = mock(ObjectFactory.class); doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) { @@ -63,12 +69,13 @@ public Object answer(InvocationOnMock invocation) { glue.addBeforeHook(beforeHook); return null; } - }).when(backend).loadGlue(any(Glue.class), ArgumentMatchers.anyList()); + }).when(backend).loadGlue(any(Glue.class), ArgumentMatchers.anyList()); PickleStep step = mock(PickleStep.class); + when(step.getText()).thenReturn("some step"); - new Runner(bus, singletonList(backend), runtimeOptions).runPickle(createPickleEventWithSteps(asList(step))); + new Runner(bus, singletonList(backend), objectFactory, typeRegistryConfigurer, runtimeOptions).runPickle(createPickleEventWithSteps(asList(step))); InOrder inOrder = inOrder(beforeHook, afterHook, backend); inOrder.verify(backend).buildWorld(); @@ -79,11 +86,11 @@ public Object answer(InvocationOnMock invocation) { @Test public void steps_are_skipped_after_failure() throws Throwable { - final StepDefinition stepDefinition = mock(StepDefinition.class); - PickleEvent pickleEventMatchingStepDefinitions = createPickleEventMatchingStepDefinitions(asList(stepDefinition)); + StubStepDefinition stepDefinition = spy(new StubStepDefinition("some step")); + PickleEvent pickleEventMatchingStepDefinitions = createPickleEventMatchingStepDefinitions(stepDefinition); final HookDefinition failingBeforeHook = addBeforeHook(); - doThrow(RuntimeException.class).when(failingBeforeHook).execute(ArgumentMatchers.any()); + doThrow(RuntimeException.class).when(failingBeforeHook).execute(ArgumentMatchers.any()); TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { @@ -101,9 +108,15 @@ public void loadGlue(Glue glue, List gluePaths) { @Test public void aftersteps_are_executed_after_failed_step() throws Throwable { - final StepDefinition stepDefinition = mock(StepDefinition.class); - doThrow(RuntimeException.class).when(stepDefinition).execute(ArgumentMatchers.any()); - PickleEvent pickleEventMatchingStepDefinitions = createPickleEventMatchingStepDefinitions(asList(stepDefinition)); + StubStepDefinition stepDefinition = spy(new StubStepDefinition("some step") { + + @Override + public void execute(Object[] args) { + throw new RuntimeException(); + } + }); + + PickleEvent pickleEventMatchingStepDefinitions = createPickleEventMatchingStepDefinitions(stepDefinition); final HookDefinition afteStepHook = addAfterStepHook(); @@ -124,11 +137,11 @@ public void loadGlue(Glue glue, List gluePaths) { @Test public void aftersteps_executed_for_passed_step() throws Throwable { - final StepDefinition stepDefinition = mock(StepDefinition.class); - PickleEvent pickleEvent = createPickleEventMatchingStepDefinitions(asList(stepDefinition)); + StubStepDefinition stepDefinition = spy(new StubStepDefinition("some step")); + PickleEvent pickleEvent = createPickleEventMatchingStepDefinitions(stepDefinition); - final HookDefinition afteStepHook1 = addAfterStepHook(); - final HookDefinition afteStepHook2 = addAfterStepHook(); + HookDefinition afteStepHook1 = addAfterStepHook(); + HookDefinition afteStepHook2 = addAfterStepHook(); TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override @@ -164,6 +177,7 @@ public void loadGlue(Glue glue, List gluePaths) { }; PickleStep step = mock(PickleStep.class); + when(step.getText()).thenReturn("some step"); runnerSupplier.get().runPickle(createPickleEventWithSteps(asList(step))); InOrder inOrder = inOrder(failingBeforeHook, beforeHook, afterHook); @@ -173,25 +187,25 @@ public void loadGlue(Glue glue, List gluePaths) { } @Test - public void steps_are_executed() throws Throwable { - final StepDefinition stepDefinition = mock(StepDefinition.class); - PickleEvent pickleEventMatchingStepDefinitions = createPickleEventMatchingStepDefinitions(asList(stepDefinition)); - TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions){ + public void steps_are_executed() { + StubStepDefinition stepDefinition = new StubStepDefinition("some step"); + PickleEvent pickleEventMatchingStepDefinitions = createPickleEventMatchingStepDefinitions(stepDefinition); + TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { glue.addStepDefinition(stepDefinition); } }; runnerSupplier.get().runPickle(pickleEventMatchingStepDefinitions); - verify(stepDefinition).execute(any(Object[].class)); + assertEquals(emptyList(), stepDefinition.getArgs()); } @Test - public void steps_are_not_executed_on_dry_run() throws Throwable { - final StepDefinition stepDefinition = mock(StepDefinition.class); - final PickleEvent pickleEvent = createPickleEventMatchingStepDefinitions(asList(stepDefinition)); + public void steps_are_not_executed_on_dry_run() { + StubStepDefinition stepDefinition = new StubStepDefinition("some step"); + PickleEvent pickleEvent = createPickleEventMatchingStepDefinitions(stepDefinition); RuntimeOptions runtimeOptions = new RuntimeOptionsBuilder().setDryRun().build(); - TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions){ + TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { glue.addStepDefinition(stepDefinition); @@ -199,7 +213,7 @@ public void loadGlue(Glue glue, List gluePaths) { }; runnerSupplier.get().runPickle(pickleEvent); - verify(stepDefinition, never()).execute(any(Object[].class)); + assertNull(stepDefinition.getArgs()); } @Test @@ -210,7 +224,7 @@ public void hooks_not_executed_in_dry_run_mode() throws Throwable { final HookDefinition afterHook = addAfterHook(); final HookDefinition afterStepHook = addAfterStepHook(); - TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions){ + TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { @@ -220,6 +234,7 @@ public void loadGlue(Glue glue, List gluePaths) { } }; PickleStep step = mock(PickleStep.class); + when(step.getText()).thenReturn("some step"); runnerSupplier.get().runPickle(createPickleEventWithSteps(asList(step))); @@ -234,7 +249,7 @@ public void hooks_not_executed_for_empty_pickles() throws Throwable { final HookDefinition afterHook = addAfterHook(); final HookDefinition afterStepHook = addAfterStepHook(); - TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions){ + TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { @@ -254,10 +269,13 @@ public void loadGlue(Glue glue, List gluePaths) { @Test public void backends_are_asked_for_snippets_for_undefined_steps() { PickleStep step = mock(PickleStep.class); + when(step.getText()).thenReturn("some step"); Backend backend = mock(Backend.class); - Runner runner = new Runner(bus, singletonList(backend), runtimeOptions); + when(backend.getSnippet()).thenReturn(new TestSnippet()); + ObjectFactory objectFactory = mock(ObjectFactory.class); + Runner runner = new Runner(bus, singletonList(backend), objectFactory, typeRegistryConfigurer, runtimeOptions); runner.runPickle(createPickleEventWithSteps(asList(step))); - verify(backend).getSnippet(ArgumentMatchers.eq(step), anyString(), any(FunctionNameGenerator.class)); + verify(backend).getSnippet(); } private HookDefinition addBeforeHook() { @@ -274,7 +292,7 @@ private HookDefinition addAfterStepHook() { private HookDefinition addHook() { HookDefinition hook = mock(HookDefinition.class); - when(hook.matches(ArgumentMatchers.anyList())).thenReturn(true); + when(hook.matches(ArgumentMatchers.anyList())).thenReturn(true); return hook; } @@ -282,16 +300,11 @@ private PickleEvent createEmptyPickleEvent() { return new PickleEvent("uri", new Pickle(NAME, ENGLISH, NO_STEPS, NO_TAGS, MOCK_LOCATIONS)); } - private PickleEvent createPickleEventMatchingStepDefinitions(List stepDefinitions) { - List steps = new ArrayList<>(stepDefinitions.size()); - int i = 0; - for (StepDefinition stepDefinition : stepDefinitions) { - PickleStep step = mock(PickleStep.class); - steps.add(step); - when(stepDefinition.matchedArguments(step)).thenReturn(Collections.emptyList()); - when(stepDefinition.getPattern()).thenReturn("pattern" + Integer.toString(++i)); - } - return new PickleEvent("uri", new Pickle(NAME, ENGLISH, steps, NO_TAGS, MOCK_LOCATIONS)); + private PickleEvent createPickleEventMatchingStepDefinitions(StubStepDefinition stepDefinition) { + PickleStep step = mock(PickleStep.class); + String pattern = stepDefinition.getPattern(); + when(step.getText()).thenReturn(pattern); + return new PickleEvent("uri", new Pickle(NAME, ENGLISH, singletonList(step), NO_TAGS, MOCK_LOCATIONS)); } private PickleEvent createPickleEventWithSteps(List steps) { diff --git a/core/src/test/java/cucumber/runner/ScenarioResultTest.java b/core/src/test/java/io/cucumber/core/runner/ScenarioResultTest.java similarity index 60% rename from core/src/test/java/cucumber/runner/ScenarioResultTest.java rename to core/src/test/java/io/cucumber/core/runner/ScenarioResultTest.java index 63a9b7670f..544314ae89 100644 --- a/core/src/test/java/cucumber/runner/ScenarioResultTest.java +++ b/core/src/test/java/io/cucumber/core/runner/ScenarioResultTest.java @@ -1,15 +1,20 @@ -package cucumber.runner; +package io.cucumber.core.runner; -import cucumber.api.Result; -import cucumber.api.event.EmbedEvent; -import cucumber.api.event.WriteEvent; +import io.cucumber.core.event.EmbedEvent; +import io.cucumber.core.event.Result; +import io.cucumber.core.event.Status; +import io.cucumber.core.event.WriteEvent; import gherkin.events.PickleEvent; +import io.cucumber.core.eventbus.EventBus; +import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentMatcher; +import java.time.Instant; import java.util.Arrays; import java.util.Collections; +import static java.time.Duration.ZERO; import static org.hamcrest.CoreMatchers.sameInstance; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -18,6 +23,7 @@ import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class ScenarioResultTest { @@ -33,52 +39,57 @@ public class ScenarioResultTest { ) ); + @Before + public void setup(){ + when(bus.getInstant()).thenReturn(Instant.now()); + } + @Test public void no_steps_is_undefined() { - assertEquals(Result.Type.UNDEFINED, s.getStatus()); + assertEquals(Status.UNDEFINED, s.getStatus()); } @Test public void one_passed_step_is_passed() { - s.add(new Result(Result.Type.PASSED, 0L, null)); - assertEquals(Result.Type.PASSED, s.getStatus()); + s.add(new Result(Status.PASSED, ZERO, null)); + assertEquals(Status.PASSED, s.getStatus()); } @Test public void passed_failed_pending_undefined_skipped_is_failed() { - s.add(new Result(Result.Type.PASSED, 0L, null)); - s.add(new Result(Result.Type.FAILED, 0L, null)); - s.add(new Result(Result.Type.PENDING, 0L, null)); - s.add(new Result(Result.Type.UNDEFINED, 0L, null)); - s.add(new Result(Result.Type.SKIPPED, 0L, null)); - assertEquals(Result.Type.FAILED, s.getStatus()); + s.add(new Result(Status.PASSED, ZERO, null)); + s.add(new Result(Status.FAILED, ZERO, null)); + s.add(new Result(Status.PENDING, ZERO, null)); + s.add(new Result(Status.UNDEFINED, ZERO, null)); + s.add(new Result(Status.SKIPPED, ZERO, null)); + assertEquals(Status.FAILED, s.getStatus()); assertTrue(s.isFailed()); } @Test public void passed_and_skipped_is_skipped_although_we_cant_have_skipped_without_undefined_or_pending() { - s.add(new Result(Result.Type.PASSED, 0L, null)); - s.add(new Result(Result.Type.SKIPPED, 0L, null)); - assertEquals(Result.Type.SKIPPED, s.getStatus()); + s.add(new Result(Status.PASSED, ZERO, null)); + s.add(new Result(Status.SKIPPED, ZERO, null)); + assertEquals(Status.SKIPPED, s.getStatus()); assertFalse(s.isFailed()); } @Test public void passed_pending_undefined_skipped_is_pending() { - s.add(new Result(Result.Type.PASSED, 0L, null)); - s.add(new Result(Result.Type.UNDEFINED, 0L, null)); - s.add(new Result(Result.Type.PENDING, 0L, null)); - s.add(new Result(Result.Type.SKIPPED, 0L, null)); - assertEquals(Result.Type.UNDEFINED, s.getStatus()); + s.add(new Result(Status.PASSED, ZERO, null)); + s.add(new Result(Status.UNDEFINED, ZERO, null)); + s.add(new Result(Status.PENDING, ZERO, null)); + s.add(new Result(Status.SKIPPED, ZERO, null)); + assertEquals(Status.UNDEFINED, s.getStatus()); assertFalse(s.isFailed()); } @Test public void passed_undefined_skipped_is_undefined() { - s.add(new Result(Result.Type.PASSED, 0L, null)); - s.add(new Result(Result.Type.UNDEFINED, 0L, null)); - s.add(new Result(Result.Type.SKIPPED, 0L, null)); - assertEquals(Result.Type.UNDEFINED, s.getStatus()); + s.add(new Result(Status.PASSED, ZERO, null)); + s.add(new Result(Status.UNDEFINED, ZERO, null)); + s.add(new Result(Status.SKIPPED, ZERO, null)); + assertEquals(Status.UNDEFINED, s.getStatus()); assertFalse(s.isFailed()); } @@ -100,8 +111,8 @@ public void failed_followed_by_pending_yields_failed_error() { Throwable failedError = mock(Throwable.class); Throwable pendingError = mock(Throwable.class); - s.add(new Result(Result.Type.FAILED, 0L, failedError)); - s.add(new Result(Result.Type.PENDING, 0L, pendingError)); + s.add(new Result(Status.FAILED, ZERO, failedError)); + s.add(new Result(Status.PENDING, ZERO, pendingError)); assertThat(s.getError(), sameInstance(failedError)); } @@ -111,8 +122,8 @@ public void pending_followed_by_failed_yields_failed_error() { Throwable pendingError = mock(Throwable.class); Throwable failedError = mock(Throwable.class); - s.add(new Result(Result.Type.PENDING, 0L, pendingError)); - s.add(new Result(Result.Type.FAILED, 0L, failedError)); + s.add(new Result(Status.PENDING, ZERO, pendingError)); + s.add(new Result(Status.FAILED, ZERO, failedError)); assertThat(s.getError(), sameInstance(failedError)); } @@ -129,7 +140,7 @@ private final class EmbedEventMatcher implements ArgumentMatcher { @Override public boolean matches(EmbedEvent argument) { return (argument != null && - Arrays.equals(argument.data, data) && argument.mimeType.equals(mimeType)); + Arrays.equals(argument.getData(), data) && argument.getMimeType().equals(mimeType)); } } @@ -142,7 +153,7 @@ private final class WriteEventMatcher implements ArgumentMatcher { @Override public boolean matches(WriteEvent argument) { - return (argument != null && argument.text.equals(text)); + return (argument != null && argument.getText().equals(text)); } } } \ No newline at end of file diff --git a/core/src/test/java/cucumber/runner/ScenarioTest.java b/core/src/test/java/io/cucumber/core/runner/ScenarioTest.java similarity index 94% rename from core/src/test/java/cucumber/runner/ScenarioTest.java rename to core/src/test/java/io/cucumber/core/runner/ScenarioTest.java index 5eefe99e8d..9c5192a778 100644 --- a/core/src/test/java/cucumber/runner/ScenarioTest.java +++ b/core/src/test/java/io/cucumber/core/runner/ScenarioTest.java @@ -1,10 +1,11 @@ -package cucumber.runner; +package io.cucumber.core.runner; import gherkin.events.PickleEvent; import gherkin.pickles.Pickle; import gherkin.pickles.PickleLocation; import gherkin.pickles.PickleStep; import gherkin.pickles.PickleTag; +import io.cucumber.core.eventbus.EventBus; import org.junit.Test; import java.util.Collections; @@ -13,7 +14,6 @@ import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; public class ScenarioTest { @@ -29,7 +29,7 @@ public void provides_the_scenario_line() { List scenarioLocation = asList(new PickleLocation(line(3), column(2))); Scenario scenario = createScenarioWithScenarioLocations(scenarioLocation); - assertEquals(asList(3), scenario.getLines()); + assertEquals((Integer) 3, scenario.getLine()); } @Test @@ -37,7 +37,7 @@ public void provides_both_the_example_row_line_and_scenario_outline_line_for_sce List scenarioLocation = asList(new PickleLocation(line(8), column(4)), new PickleLocation(line(3), column(2))); Scenario scenario = createScenarioWithScenarioLocations(scenarioLocation); - assertEquals(asList(8, 3), scenario.getLines()); + assertEquals((Integer) 8, scenario.getLine()); } @Test diff --git a/core/src/test/java/cucumber/runner/StepDefinitionMatchTest.java b/core/src/test/java/io/cucumber/core/runner/StepDefinitionMatchTest.java similarity index 81% rename from core/src/test/java/cucumber/runner/StepDefinitionMatchTest.java rename to core/src/test/java/io/cucumber/core/runner/StepDefinitionMatchTest.java index c93c9c7383..07c89e8e4d 100644 --- a/core/src/test/java/cucumber/runner/StepDefinitionMatchTest.java +++ b/core/src/test/java/io/cucumber/core/runner/StepDefinitionMatchTest.java @@ -1,19 +1,18 @@ -package cucumber.runner; +package io.cucumber.core.runner; -import cucumber.runtime.StepDefinition; -import cucumber.runtime.StepDefinitionMatch; -import cucumber.runtime.StubStepDefinition; -import io.cucumber.stepexpression.TypeRegistry; import gherkin.pickles.PickleCell; import gherkin.pickles.PickleLocation; import gherkin.pickles.PickleRow; import gherkin.pickles.PickleStep; import gherkin.pickles.PickleTable; +import io.cucumber.core.backend.StepDefinition; +import io.cucumber.core.runtime.StubStepDefinition; +import io.cucumber.core.stepexpression.Argument; +import io.cucumber.core.stepexpression.TypeRegistry; import io.cucumber.cucumberexpressions.ParameterType; import io.cucumber.cucumberexpressions.Transformer; import io.cucumber.datatable.DataTableType; import io.cucumber.datatable.TableCellTransformer; -import io.cucumber.stepexpression.Argument; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -36,8 +35,9 @@ public class StepDefinitionMatchTest { public void executes_a_step() throws Throwable { PickleStep step = new PickleStep("I have 4 cukes in my belly", Collections.emptyList(), asList(mock(PickleLocation.class))); - StepDefinition stepDefinition = new StubStepDefinition("I have {int} cukes in my belly", typeRegistry, Integer.class); - List arguments = stepDefinition.matchedArguments(step); + StepDefinition stepDefinition = new StubStepDefinition("I have {int} cukes in my belly", Integer.class); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, typeRegistry); + List arguments = coreStepDefinition.matchedArguments(step); StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); stepDefinitionMatch.runStep(null); } @@ -46,8 +46,9 @@ public void executes_a_step() throws Throwable { public void throws_arity_mismatch_exception_when_there_are_fewer_parameters_than_arguments() throws Throwable { PickleStep step = new PickleStep("I have 4 cukes in my belly", Collections.emptyList(), asList(mock(PickleLocation.class))); - StepDefinition stepDefinition = new StubStepDefinition("I have {int} cukes in my belly", typeRegistry); - List arguments = stepDefinition.matchedArguments(step); + StepDefinition stepDefinition = new StubStepDefinition("I have {int} cukes in my belly"); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, typeRegistry); + List arguments = coreStepDefinition.matchedArguments(step); StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); @@ -71,8 +72,9 @@ public void throws_arity_mismatch_exception_when_there_are_fewer_parameters_than PickleStep step = new PickleStep("I have 4 cukes in my belly", asList((gherkin.pickles.Argument) table), asList(mock(PickleLocation.class))); - StepDefinition stepDefinition = new StubStepDefinition("I have {int} cukes in my belly", typeRegistry); - List arguments = stepDefinition.matchedArguments(step); + StepDefinition stepDefinition = new StubStepDefinition("I have {int} cukes in my belly"); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, typeRegistry); + List arguments = coreStepDefinition.matchedArguments(step); PickleStepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); expectedException.expectMessage( @@ -91,8 +93,9 @@ public void throws_arity_mismatch_exception_when_there_are_fewer_parameters_than @Test public void throws_arity_mismatch_exception_when_there_are_more_parameters_than_arguments() throws Throwable { PickleStep step = new PickleStep("I have 4 cukes in my belly", asList((gherkin.pickles.Argument) mock(PickleTable.class)), asList(mock(PickleLocation.class))); - StepDefinition stepDefinition = new StubStepDefinition("I have {int} cukes in my belly", typeRegistry, Integer.TYPE, Short.TYPE, List.class); - List arguments = stepDefinition.matchedArguments(step); + StepDefinition stepDefinition = new StubStepDefinition("I have {int} cukes in my belly", Integer.TYPE, Short.TYPE, List.class); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, typeRegistry); + List arguments = coreStepDefinition.matchedArguments(step); PickleStepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); expectedException.expectMessage("" + "Step [I have {int} cukes in my belly] is defined with 3 parameters at '{stubbed location with details}'.\n" + @@ -108,8 +111,9 @@ public void throws_arity_mismatch_exception_when_there_are_more_parameters_than_ @Test public void throws_arity_mismatch_exception_when_there_are_more_parameters_and_no_arguments() throws Throwable { PickleStep step = new PickleStep("I have cukes in my belly", Collections.emptyList(), asList(mock(PickleLocation.class))); - StepDefinition stepDefinition = new StubStepDefinition("I have cukes in my belly", typeRegistry, Integer.TYPE, Short.TYPE, List.class); - List arguments = stepDefinition.matchedArguments(step); + StepDefinition stepDefinition = new StubStepDefinition("I have cukes in my belly", Integer.TYPE, Short.TYPE, List.class); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, typeRegistry); + List arguments = coreStepDefinition.matchedArguments(step); StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); expectedException.expectMessage("" + "Step [I have cukes in my belly] is defined with 3 parameters at '{stubbed location with details}'.\n" + @@ -124,8 +128,9 @@ public void throws_register_type_in_configuration_exception_when_there_is_no_dat PickleTable table = new PickleTable(singletonList(new PickleRow(singletonList(new PickleCell(mock(PickleLocation.class), "A"))))); PickleStep step = new PickleStep("I have a datatable", asList((gherkin.pickles.Argument) table), asList(mock(PickleLocation.class))); - StepDefinition stepDefinition = new StubStepDefinition("I have a datatable", typeRegistry, UndefinedDataTableType.class); - List arguments = stepDefinition.matchedArguments(step); + StepDefinition stepDefinition = new StubStepDefinition("I have a datatable", UndefinedDataTableType.class); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, typeRegistry); + List arguments = coreStepDefinition.matchedArguments(step); StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); expectedException.expectMessage("" + @@ -149,8 +154,9 @@ public ItemQuantity transform(String s) throws Throwable { })); PickleStep step = new PickleStep("I have some cukes in my belly", Collections.emptyList(), asList(mock(PickleLocation.class))); - StepDefinition stepDefinition = new StubStepDefinition("I have {itemQuantity} in my belly", typeRegistry, ItemQuantity.class); - List arguments = stepDefinition.matchedArguments(step); + StepDefinition stepDefinition = new StubStepDefinition("I have {itemQuantity} in my belly", ItemQuantity.class); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, typeRegistry); + List arguments = coreStepDefinition.matchedArguments(step); StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); expectedException.expectMessage("" + @@ -182,8 +188,9 @@ public ItemQuantity transform(String s) { )); PickleStep step = new PickleStep("I have some cukes in my belly", singletonList((gherkin.pickles.Argument) table), asList(mock(PickleLocation.class))); - StepDefinition stepDefinition = new StubStepDefinition("I have some cukes in my belly", typeRegistry, ItemQuantity.class); - List arguments = stepDefinition.matchedArguments(step); + StepDefinition stepDefinition = new StubStepDefinition("I have some cukes in my belly", ItemQuantity.class); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, typeRegistry); + List arguments = coreStepDefinition.matchedArguments(step); StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); expectedException.expectMessage("" + diff --git a/core/src/test/java/io/cucumber/core/runner/StepDurationTimeService.java b/core/src/test/java/io/cucumber/core/runner/StepDurationTimeService.java new file mode 100644 index 0000000000..c3c4c71d46 --- /dev/null +++ b/core/src/test/java/io/cucumber/core/runner/StepDurationTimeService.java @@ -0,0 +1,55 @@ +package io.cucumber.core.runner; + +import io.cucumber.core.event.EventHandler; +import io.cucumber.core.plugin.EventListener; +import io.cucumber.core.event.EventPublisher; +import io.cucumber.core.event.TestStepStarted; + +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneId; + +public class StepDurationTimeService extends Clock implements EventListener { + + private final ThreadLocal currentInstant = new ThreadLocal<>(); + private final Duration stepDuration; + + private EventHandler stepStartedHandler = new EventHandler() { + @Override + public void receive(TestStepStarted event) { + handleTestStepStarted(); + } + }; + + public StepDurationTimeService(Duration stepDuration) { + this.stepDuration = stepDuration; + } + + @Override + public void setEventPublisher(EventPublisher publisher) { + publisher.registerHandlerFor(TestStepStarted.class, stepStartedHandler); + } + + private void handleTestStepStarted() { + Instant timeInstant = instant(); + currentInstant.set(timeInstant.plus(stepDuration)); + } + + @Override + public ZoneId getZone() { + return null; + } + + @Override + public Clock withZone(ZoneId zone) { + return null; + } + + @Override + public Instant instant() { + Instant result = currentInstant.get(); + return result != null ? result : Instant.EPOCH; + } + +} diff --git a/core/src/test/java/io/cucumber/core/runner/StubStepDefinition.java b/core/src/test/java/io/cucumber/core/runner/StubStepDefinition.java new file mode 100644 index 0000000000..5b9e204a6f --- /dev/null +++ b/core/src/test/java/io/cucumber/core/runner/StubStepDefinition.java @@ -0,0 +1,86 @@ +package io.cucumber.core.runner; + +import io.cucumber.core.backend.ParameterInfo; +import io.cucumber.core.backend.StepDefinition; +import io.cucumber.core.backend.TypeResolver; + +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.junit.Assert.assertEquals; + +class StubStepDefinition implements StepDefinition { + private final List parameterInfos; + private final String expression; + private final boolean transposed; + + private List args; + + StubStepDefinition(String pattern, Type... types) { + this(pattern, false, types); + } + + StubStepDefinition(String pattern, boolean transposed, Type... types) { + this.parameterInfos = Stream.of(types).map(StubParameterInfo::new).collect(Collectors.toList()); + this.expression = pattern; + this.transposed = transposed; + } + + @Override + public String getLocation(boolean detail) { + return "{stubbed location" + (detail ? " with details" : "") + "}"; + } + + @Override + public void execute(Object[] args) throws Throwable { + assertEquals(parameterInfos.size(), args.length); + this.args = Arrays.asList(args); + } + + public List getArgs() { + return args; + } + + @Override + public boolean isDefinedAt(StackTraceElement stackTraceElement) { + return false; + } + + @Override + public List parameterInfos() { + return parameterInfos; + } + + @Override + public String getPattern() { + return expression; + } + + private final class StubParameterInfo implements ParameterInfo { + + private final Type type; + + private StubParameterInfo(Type type) { + this.type = type; + } + + @Override + public Type getType() { + return type; + } + + @Override + public boolean isTransposed() { + return transposed; + } + + @Override + public TypeResolver getTypeResolver() { + return () -> type; + } + } + +} diff --git a/core/src/test/java/cucumber/runner/TestBackendSupplier.java b/core/src/test/java/io/cucumber/core/runner/TestBackendSupplier.java similarity index 60% rename from core/src/test/java/cucumber/runner/TestBackendSupplier.java rename to core/src/test/java/io/cucumber/core/runner/TestBackendSupplier.java index b91001a264..f4124435e7 100644 --- a/core/src/test/java/cucumber/runner/TestBackendSupplier.java +++ b/core/src/test/java/io/cucumber/core/runner/TestBackendSupplier.java @@ -1,9 +1,11 @@ -package cucumber.runner; +package io.cucumber.core.runner; -import cucumber.runtime.Backend; -import cucumber.runtime.BackendSupplier; -import cucumber.runtime.snippets.FunctionNameGenerator; +import io.cucumber.core.snippets.Snippet; +import io.cucumber.core.snippets.SnippetType; +import io.cucumber.core.backend.Backend; +import io.cucumber.core.runtime.BackendSupplier; import gherkin.pickles.PickleStep; +import io.cucumber.core.snippets.TestSnippet; import java.util.Collection; import java.util.Collections; @@ -24,10 +26,11 @@ public void disposeWorld() { } @Override - public List getSnippet(PickleStep step, String keyword, FunctionNameGenerator functionNameGenerator) { - return emptyList(); + public Snippet getSnippet() { + return new TestSnippet(); } + @Override public Collection get() { return Collections.singleton(this); diff --git a/core/src/test/java/cucumber/runner/TestCaseTest.java b/core/src/test/java/io/cucumber/core/runner/TestCaseTest.java similarity index 82% rename from core/src/test/java/cucumber/runner/TestCaseTest.java rename to core/src/test/java/io/cucumber/core/runner/TestCaseTest.java index 76efeb2b33..83ff839795 100644 --- a/core/src/test/java/cucumber/runner/TestCaseTest.java +++ b/core/src/test/java/io/cucumber/core/runner/TestCaseTest.java @@ -1,21 +1,25 @@ -package cucumber.runner; +package io.cucumber.core.runner; -import cucumber.api.Scenario; -import cucumber.api.event.TestCaseFinished; -import cucumber.api.event.TestCaseStarted; -import cucumber.runtime.HookDefinition; +import io.cucumber.core.api.Scenario; +import io.cucumber.core.event.TestCaseFinished; +import io.cucumber.core.event.TestCaseStarted; +import io.cucumber.core.backend.HookDefinition; import gherkin.events.PickleEvent; import gherkin.pickles.Pickle; import gherkin.pickles.PickleLocation; import gherkin.pickles.PickleStep; +import io.cucumber.core.eventbus.EventBus; + +import org.junit.Before; import org.junit.Test; import org.mockito.InOrder; +import org.mockito.Mockito; -import java.util.Arrays; +import java.time.Instant; import java.util.Collections; -import static cucumber.api.HookType.AfterStep; -import static cucumber.api.HookType.BeforeStep; +import static io.cucumber.core.event.HookType.AFTER_STEP; +import static io.cucumber.core.event.HookType.BEFORE_STEP; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.mockito.ArgumentMatchers.isA; @@ -34,11 +38,16 @@ public class TestCaseTest { private HookDefinition beforeStep1HookDefinition1 = mock(HookDefinition.class); private HookDefinition afterStep1HookDefinition1 = mock(HookDefinition.class); + @Before + public void init() { + Mockito.when(bus.getInstant()).thenReturn(Instant.now()); + } + private final PickleStepTestStep testStep1 = new PickleStepTestStep( "uri", mock(PickleStep.class), - singletonList(new HookTestStep(BeforeStep, new HookDefinitionMatch(beforeStep1HookDefinition1))), - singletonList(new HookTestStep(AfterStep, new HookDefinitionMatch(afterStep1HookDefinition1))), + singletonList(new HookTestStep(BEFORE_STEP, new HookDefinitionMatch(beforeStep1HookDefinition1))), + singletonList(new HookTestStep(AFTER_STEP, new HookDefinitionMatch(afterStep1HookDefinition1))), definitionMatch1 ); @@ -49,8 +58,8 @@ public class TestCaseTest { private final PickleStepTestStep testStep2 = new PickleStepTestStep( "uri", mock(PickleStep.class), - singletonList(new HookTestStep(BeforeStep, new HookDefinitionMatch(beforeStep1HookDefinition2))), - singletonList(new HookTestStep(AfterStep, new HookDefinitionMatch(afterStep1HookDefinition2))), + singletonList(new HookTestStep(BEFORE_STEP, new HookDefinitionMatch(beforeStep1HookDefinition2))), + singletonList(new HookTestStep(AFTER_STEP, new HookDefinitionMatch(afterStep1HookDefinition2))), definitionMatch2 ); diff --git a/core/src/test/java/io/cucumber/core/runner/TestDefinitionArgument.java b/core/src/test/java/io/cucumber/core/runner/TestDefinitionArgument.java new file mode 100644 index 0000000000..840dd39594 --- /dev/null +++ b/core/src/test/java/io/cucumber/core/runner/TestDefinitionArgument.java @@ -0,0 +1,12 @@ +package io.cucumber.core.runner; + +import io.cucumber.core.event.Argument; + +import java.util.List; + +public class TestDefinitionArgument { + + public static List createArguments(List match) { + return DefinitionArgument.createArguments(match); + } +} \ No newline at end of file diff --git a/core/src/test/java/cucumber/runner/TestHelper.java b/core/src/test/java/io/cucumber/core/runner/TestHelper.java similarity index 67% rename from core/src/test/java/cucumber/runner/TestHelper.java rename to core/src/test/java/io/cucumber/core/runner/TestHelper.java index fe596a47ee..3ecc2baf18 100644 --- a/core/src/test/java/cucumber/runner/TestHelper.java +++ b/core/src/test/java/io/cucumber/core/runner/TestHelper.java @@ -1,32 +1,33 @@ -package cucumber.runner; - -import cucumber.api.PendingException; -import cucumber.api.Plugin; -import cucumber.api.Result; -import cucumber.api.Scenario; -import cucumber.api.event.ConcurrentEventListener; -import cucumber.api.event.EventListener; -import cucumber.runtime.BackendSupplier; +package io.cucumber.core.runner; + +import io.cucumber.core.event.Result; +import io.cucumber.core.event.Status; +import io.cucumber.core.plugin.Plugin; +import io.cucumber.core.api.Scenario; +import io.cucumber.core.plugin.ConcurrentEventListener; +import io.cucumber.core.plugin.EventListener; +import io.cucumber.core.event.Event; +import io.cucumber.core.backend.Glue; +import io.cucumber.core.backend.HookDefinition; +import io.cucumber.core.backend.StepDefinition; +import io.cucumber.core.runtime.*; +import io.cucumber.core.eventbus.EventBus; +import io.cucumber.core.io.Resource; +import io.cucumber.core.io.ResourceLoader; +import io.cucumber.core.io.TestClasspathResourceLoader; +import io.cucumber.core.feature.FeatureParser; import io.cucumber.core.options.CommandlineOptionsParser; -import cucumber.runtime.FeatureSupplier; -import cucumber.runtime.Glue; -import cucumber.runtime.HookDefinition; -import cucumber.runtime.Runtime; -import cucumber.runtime.StepDefinition; -import cucumber.runtime.StubStepDefinition; -import cucumber.runtime.io.ClasspathResourceLoader; -import cucumber.runtime.io.Resource; -import cucumber.runtime.model.CucumberFeature; -import cucumber.runtime.model.FeatureParser; +import io.cucumber.core.feature.CucumberFeature; import gherkin.pickles.Compiler; import gherkin.pickles.Pickle; import gherkin.pickles.PickleStep; import gherkin.pickles.PickleString; import gherkin.pickles.PickleTable; import gherkin.pickles.PickleTag; -import io.cucumber.core.model.FeatureIdentifier; +import io.cucumber.core.feature.FeatureIdentifier; +import io.cucumber.core.runtime.Runtime; import io.cucumber.datatable.DataTable; -import io.cucumber.stepexpression.TypeRegistry; +import io.cucumber.core.stepexpression.TypeRegistry; import junit.framework.AssertionFailedError; import org.mockito.ArgumentMatchers; import org.mockito.invocation.InvocationOnMock; @@ -38,6 +39,10 @@ import java.lang.reflect.Type; import java.net.URI; import java.nio.charset.StandardCharsets; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneId; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; import java.util.Arrays; @@ -45,12 +50,14 @@ import java.util.List; import java.util.Map; -import static cucumber.api.Result.Type.FAILED; -import static cucumber.api.Result.Type.PASSED; -import static cucumber.api.Result.Type.PENDING; -import static cucumber.api.Result.Type.SKIPPED; -import static cucumber.api.Result.Type.UNDEFINED; +import static io.cucumber.core.event.Status.FAILED; +import static io.cucumber.core.event.Status.PASSED; +import static io.cucumber.core.event.Status.PENDING; +import static io.cucumber.core.event.Status.SKIPPED; +import static io.cucumber.core.event.Status.UNDEFINED; +import static java.time.Duration.ZERO; import static java.util.Locale.ENGLISH; +import static java.util.Locale.ROOT; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -66,29 +73,40 @@ public enum TimeServiceType { } private List features = Collections.emptyList(); - private Map stepsToResult = Collections.emptyMap(); + private Map stepsToResult = Collections.emptyMap(); private Map stepsToLocation = Collections.emptyMap(); - private List> hooks = Collections.emptyList(); + private List> hooks = Collections.emptyList(); private List hookLocations = Collections.emptyList(); private List> hookActions = Collections.emptyList(); private TimeServiceType timeServiceType = TimeServiceType.FIXED_INCREMENT_ON_STEP_START; - private long timeServiceIncrement = 0L; + private Duration timeServiceIncrement = Duration.ZERO; private Object formatterUnderTest = null; private List runtimeArgs = Collections.emptyList(); private TestHelper() { } - private static final class TestHelperBackendSupplier extends TestBackendSupplier { + public static final class TestHelperBackendSupplier extends TestBackendSupplier { private final List features; - private final Map stepsToResult; + private final Map stepsToResult; private final Map stepsToLocation; - private final List> hooks; + private final List> hooks; private final List hookLocations; private final List> hookActions; - private TestHelperBackendSupplier(List features, Map stepsToResult, Map stepsToLocation, List> hooks, List hookLocations, List> hookActions) { + public TestHelperBackendSupplier(ResourceLoader resourceLoader, TypeRegistry typeRegistry) { + this( + Collections.emptyList(), + Collections.emptyMap(), + Collections.emptyMap(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList() + ); + } + + public TestHelperBackendSupplier(List features, Map stepsToResult, Map stepsToLocation, List> hooks, List hookLocations, List> hookActions) { this.features = features; this.stepsToResult = stepsToResult; this.stepsToLocation = stepsToLocation; @@ -97,6 +115,17 @@ private TestHelperBackendSupplier(List features, Map features) { + this( + features, + Collections.emptyMap(), + Collections.emptyMap(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList() + ); + } + @Override public void loadGlue(Glue glue, List gluePaths) { @@ -109,7 +138,7 @@ public void loadGlue(Glue glue, List gluePaths) { } private static void mockSteps(Glue glue, List features, - Map stepsToResult, + Map stepsToResult, final Map stepsToLocation) { Compiler compiler = new Compiler(); TypeRegistry typeRegistry = new TypeRegistry(ENGLISH); @@ -126,24 +155,24 @@ private static void mockSteps(Glue glue, List features, } for (final PickleStep step : steps) { - final Result stepResult = getResultWithDefaultPassed(stepsToResult, step.getText()); - if (stepResult.is(UNDEFINED)) { + final io.cucumber.core.event.Result stepResult = getResultWithDefaultPassed(stepsToResult, step.getText()); + if (stepResult.getStatus().is(UNDEFINED)) { continue; } Type[] types = mapArgumentToTypes(step); - StepDefinition stepDefinition = new StubStepDefinition(step.getText(), typeRegistry, types) { + StepDefinition stepDefinition = new StubStepDefinition(step.getText(), types) { @Override public void execute(Object[] args) throws Throwable { super.execute(args); - if (stepResult.is(PENDING)) { - throw new PendingException(); - } else if (stepResult.is(FAILED)) { + if (stepResult.getStatus().is(PENDING)) { + throw new TestPendingException(); + } else if (stepResult.getStatus().is(FAILED)) { throw stepResult.getError(); - } else if (stepResult.is(SKIPPED) && (stepResult.getError() != null)) { + } else if (stepResult.getStatus().is(SKIPPED) && (stepResult.getError() != null)) { throw stepResult.getError(); - } else if (!stepResult.is(PASSED) && !stepResult.is(SKIPPED)) { + } else if (!stepResult.getStatus().is(PASSED) && !stepResult.getStatus().is(SKIPPED)) { fail("Cannot mock step to the result: " + stepResult.getStatus()); } } @@ -159,8 +188,8 @@ public String getLocation(boolean detail) { } - private static Result getResultWithDefaultPassed(Map stepsToResult, String step) { - return stepsToResult.containsKey(step) ? stepsToResult.get(step) : new Result(PASSED, 0L, null); + private static io.cucumber.core.event.Result getResultWithDefaultPassed(Map stepsToResult, String step) { + return stepsToResult.containsKey(step) ? stepsToResult.get(step) : new Result(PASSED, ZERO, null); } @@ -188,7 +217,7 @@ private static Type[] mapArgumentToTypes(PickleStep step) { return types; } - private static void mockHooks(cucumber.runtime.Glue glue, final List> hooks, + private static void mockHooks(Glue glue, final List> hooks, final List hookLocations, final List> hookActions) throws Throwable { List beforeHooks = new ArrayList<>(); @@ -214,7 +243,7 @@ private static void mockHooks(cucumber.runtime.Glue glue, final List hookEntry, + private static void mockHook(final SimpleEntry hookEntry, final String hookLocation, final Answer action, final List beforeHooks, @@ -229,10 +258,10 @@ private static void mockHook(final SimpleEntry hookEntry, if (action != null) { doAnswer(action).when(hook).execute((Scenario) any()); } - if (hookEntry.getValue().is(FAILED)) { - doThrow(hookEntry.getValue().getError()).when(hook).execute((cucumber.api.Scenario) any()); - } else if (hookEntry.getValue().is(PENDING)) { - doThrow(new PendingException()).when(hook).execute((cucumber.api.Scenario) any()); + if (hookEntry.getValue().getStatus().is(FAILED)) { + doThrow(hookEntry.getValue().getError()).when(hook).execute((Scenario) any()); + } else if (hookEntry.getValue().getStatus().is(PENDING)) { + doThrow(new TestPendingException()).when(hook).execute((io.cucumber.core.api.Scenario) any()); } if ("before".equals(hookEntry.getKey())) { beforeHooks.add(hook); @@ -252,7 +281,7 @@ private static void mockHook(final SimpleEntry hookEntry, public void run() { final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - final ClasspathResourceLoader resourceLoader = new ClasspathResourceLoader(classLoader); + final ResourceLoader resourceLoader = TestClasspathResourceLoader.create(classLoader); final BackendSupplier backendSupplier = new TestHelperBackendSupplier( @@ -264,14 +293,11 @@ public void run() { hookActions ); + final EventBus bus = createEventBus(); + final FeatureSupplier featureSupplier = features.isEmpty() ? null // assume feature paths passed in as args instead - : new FeatureSupplier() { - @Override - public List get() { - return features; - } - }; + : new TestFeatureSupplier(bus, features); Runtime.Builder runtimeBuilder = Runtime.builder() .withRuntimeOptions( @@ -282,33 +308,35 @@ public List get() { .withClassLoader(classLoader) .withResourceLoader(resourceLoader) .withBackendSupplier(backendSupplier) - .withFeatureSupplier(featureSupplier); - - if (TimeServiceType.REAL_TIME.equals(this.timeServiceType)) { - if (formatterUnderTest instanceof Plugin) { - runtimeBuilder.withAdditionalPlugins((Plugin) formatterUnderTest); - } - } else { - EventBus bus = null; - if (TimeServiceType.FIXED_INCREMENT_ON_STEP_START.equals(this.timeServiceType)) { - final StepDurationTimeService timeService = new StepDurationTimeService(this.timeServiceIncrement); - bus = new TimeServiceEventBus(timeService); - timeService.setEventPublisher(bus); - } else if (TimeServiceType.FIXED_INCREMENT.equals(this.timeServiceType)) { - bus = new TimeServiceEventBus(new TimeServiceStub(this.timeServiceIncrement)); - } - - runtimeBuilder.withEventBus(bus); - if (formatterUnderTest instanceof ConcurrentEventListener) { - ((ConcurrentEventListener) formatterUnderTest).setEventPublisher(bus); - } else if (formatterUnderTest instanceof EventListener) { - ((EventListener) formatterUnderTest).setEventPublisher(bus); - } + .withFeatureSupplier(featureSupplier) + .withEventBus(bus); + + if (formatterUnderTest instanceof ConcurrentEventListener) { + ((ConcurrentEventListener) formatterUnderTest).setEventPublisher(bus); + } else if (formatterUnderTest instanceof EventListener) { + ((EventListener) formatterUnderTest).setEventPublisher(bus); + } else if (formatterUnderTest instanceof Plugin) { + runtimeBuilder.withAdditionalPlugins((Plugin) formatterUnderTest); } runtimeBuilder.build().run(); } + private EventBus createEventBus() { + EventBus bus = null; + + if (TimeServiceType.REAL_TIME.equals(this.timeServiceType)) { + bus = new TimeServiceEventBus(Clock.systemUTC()); + } else if (TimeServiceType.FIXED_INCREMENT_ON_STEP_START.equals(this.timeServiceType)) { + final StepDurationTimeService timeService = new StepDurationTimeService(this.timeServiceIncrement); + bus = new TimeServiceEventBus(timeService); + timeService.setEventPublisher(bus); + } else if (TimeServiceType.FIXED_INCREMENT.equals(this.timeServiceType)) { + bus = new TimeServiceEventBus(Clock.fixed(Instant.EPOCH, ZoneId.of("UTC"))); + } + return bus; + } + public static Builder builder() { return new Builder(); } @@ -328,7 +356,7 @@ public Builder withFeatures(List features) { return this; } - public Builder withStepsToResult(Map stepsToResult) { + public Builder withStepsToResult(Map stepsToResult) { this.instance.stepsToResult = stepsToResult; return this; } @@ -338,7 +366,7 @@ public Builder withStepsToLocation(Map stepsToLocation) { return this; } - public Builder withHooks(List> hooks) { + public Builder withHooks(List> hooks) { this.instance.hooks = hooks; return this; } @@ -360,22 +388,22 @@ public Builder withHookActions(List> hookActions) { * @param timeServiceIncrement increment to be used * @return this instance */ - public Builder withTimeServiceIncrement(long timeServiceIncrement) { + public Builder withTimeServiceIncrement(Duration timeServiceIncrement) { this.instance.timeServiceIncrement = timeServiceIncrement; return this; } /** * Specifies what type of TimeService to be used by the {@link EventBus} - * {@link TimeServiceType#REAL_TIME} > {@link TimeService#SYSTEM} - * {@link TimeServiceType#FIXED_INCREMENT} > {@link TimeServiceStub} + * {@link TimeServiceType#REAL_TIME} > {@link Clock#systemUTC()} + * {@link TimeServiceType#FIXED_INCREMENT} > {@link ClockStub} * {@link TimeServiceType#FIXED_INCREMENT_ON_STEP_START} > {@link StepDurationTimeService} *

* Defaults to {@link TimeServiceType#FIXED_INCREMENT_ON_STEP_START} *

* Note: when running tests with multiple threads & not using {@link TimeServiceType#REAL_TIME} - * it can inadvertently affect the order of {@link cucumber.api.event.Event}s - * published to any {@link cucumber.api.event.ConcurrentEventListener}s used during the test run + * it can inadvertently affect the order of {@link Event}s + * published to any {@link ConcurrentEventListener}s used during the test run * * @return this instance */ @@ -385,9 +413,9 @@ public Builder withTimeServiceType(TimeServiceType timeServiceType) { } /** - * Specify a formatter under test, Formatter or ConcurrentFormatter + * Specify a plugin under test, Formatter or ConcurrentFormatter * - * @param formatter the formatter under test + * @param formatter the plugin under test * @return this instance */ public Builder withFormatterUnderTest(Object formatter) { @@ -428,29 +456,33 @@ public InputStream getInputStream() { }); } - public static Result result(String status) { - return result(Result.Type.fromLowerCaseName(status)); + public static io.cucumber.core.event.Result result(String status) { + return result(fromLowerCaseName(status)); + } + + public static io.cucumber.core.event.Result result(String status, Throwable error) { + return result(fromLowerCaseName(status), error); } - public static Result result(String status, Throwable error) { - return result(Result.Type.fromLowerCaseName(status), error); + private static Status fromLowerCaseName(String lowerCaseName) { + return Status.valueOf(lowerCaseName.toUpperCase(ROOT)); } - public static Result result(Result.Type status) { + public static io.cucumber.core.event.Result result(Status status) { switch (status) { case FAILED: return result(status, mockAssertionFailedError()); case AMBIGUOUS: return result(status, mockAmbiguousStepDefinitionException()); case PENDING: - return result(status, new PendingException()); + return result(status, new TestPendingException()); default: return result(status, null); } } - public static Result result(Result.Type status, Throwable error) { - return new Result(status, 0L, error); + public static io.cucumber.core.event.Result result(Status status, Throwable error) { + return new Result(status, Duration.ZERO, error); } public static Answer createWriteHookAction(final String output) { @@ -495,6 +527,7 @@ public Object answer(InvocationOnMock invocation) { }; doAnswer(printStackTraceHandler).when(error).printStackTrace((PrintWriter) any()); when(error.getStackTrace()).thenReturn(new StackTraceElement[0]); + when(error.getMessage()).thenReturn("the message"); return error; } @@ -509,10 +542,11 @@ public Object answer(InvocationOnMock invocation) { } }; doAnswer(printStackTraceHandler).when(exception).printStackTrace((PrintWriter) any()); + when(exception.getMessage()).thenReturn("the message"); return exception; } - public static SimpleEntry hookEntry(String type, Result result) { + public static SimpleEntry hookEntry(String type, io.cucumber.core.event.Result result) { return new SimpleEntry<>(type, result); } diff --git a/core/src/test/java/io/cucumber/core/runner/TestPendingException.java b/core/src/test/java/io/cucumber/core/runner/TestPendingException.java new file mode 100644 index 0000000000..ed61b383e6 --- /dev/null +++ b/core/src/test/java/io/cucumber/core/runner/TestPendingException.java @@ -0,0 +1,14 @@ +package io.cucumber.core.runner; + +import io.cucumber.core.backend.Pending; + +@Pending +public final class TestPendingException extends RuntimeException { + public TestPendingException() { + this("TODO: implement me"); + } + + public TestPendingException(String message) { + super(message); + } +} diff --git a/core/src/test/java/io/cucumber/core/runner/TestRunnerSupplier.java b/core/src/test/java/io/cucumber/core/runner/TestRunnerSupplier.java new file mode 100644 index 0000000000..f68766e68d --- /dev/null +++ b/core/src/test/java/io/cucumber/core/runner/TestRunnerSupplier.java @@ -0,0 +1,71 @@ +package io.cucumber.core.runner; + +import io.cucumber.core.backend.Backend; +import io.cucumber.core.backend.Glue; +import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.eventbus.EventBus; +import io.cucumber.core.options.RuntimeOptions; +import io.cucumber.core.runtime.RunnerSupplier; +import io.cucumber.core.snippets.Snippet; +import io.cucumber.core.snippets.TestSnippet; + +import java.net.URI; +import java.util.List; + +import static java.util.Collections.singleton; + +public class TestRunnerSupplier implements Backend, RunnerSupplier, ObjectFactory { + + private final EventBus bus; + private final RuntimeOptions runtimeOptions; + + protected TestRunnerSupplier(EventBus bus, RuntimeOptions runtimeOptions) { + this.bus = bus; + this.runtimeOptions = runtimeOptions; + } + + @Override + public void loadGlue(Glue glue, List gluePaths) { + + } + + @Override + public void buildWorld() { + + } + + @Override + public void disposeWorld() { + + } + + @Override + public Snippet getSnippet() { + return new TestSnippet(); + } + + @Override + public Runner get() { + return new Runner(bus, singleton(this), this, typeRegistry -> {}, runtimeOptions); + } + + @Override + public void start() { + + } + + @Override + public void stop() { + + } + + @Override + public boolean addClass(Class glueClass) { + return false; + } + + @Override + public T getInstance(Class glueClass) { + return null; + } +} diff --git a/core/src/test/java/io/cucumber/core/runner/UndefinedStepDefinitionMatchTest.java b/core/src/test/java/io/cucumber/core/runner/UndefinedStepDefinitionMatchTest.java new file mode 100644 index 0000000000..2b622a216a --- /dev/null +++ b/core/src/test/java/io/cucumber/core/runner/UndefinedStepDefinitionMatchTest.java @@ -0,0 +1,33 @@ +package io.cucumber.core.runner; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; + +import org.junit.Test; +import org.junit.jupiter.api.function.Executable; + +import gherkin.pickles.PickleStep; +import io.cucumber.core.api.Scenario; + +public class UndefinedStepDefinitionMatchTest { + + public final UndefinedPickleStepDefinitionMatch match = new UndefinedPickleStepDefinitionMatch(mock(PickleStep.class)); + + @Test + public void throws_ambiguous_step_definitions_exception_when_run() { + final Executable testMethod = () -> match.runStep(mock(Scenario.class)); + final UndefinedStepDefinitionException expectedThrown = assertThrows(UndefinedStepDefinitionException.class, testMethod); + assertThat(expectedThrown.getMessage(), is(equalTo("No step definitions found"))); + } + + @Test + public void throws_ambiguous_step_definitions_exception_when_dry_run() { + final Executable testMethod = () -> match.dryRunStep(mock(Scenario.class)); + final UndefinedStepDefinitionException expectedThrown = assertThrows(UndefinedStepDefinitionException.class, testMethod); + assertThat(expectedThrown.getMessage(), is(equalTo("No step definitions found"))); + } + +} diff --git a/core/src/test/java/cucumber/runtime/BackendModuleBackendSupplierTest.java b/core/src/test/java/io/cucumber/core/runtime/BackendServiceLoaderTest.java similarity index 51% rename from core/src/test/java/cucumber/runtime/BackendModuleBackendSupplierTest.java rename to core/src/test/java/io/cucumber/core/runtime/BackendServiceLoaderTest.java index a687385dd4..80fecdd582 100644 --- a/core/src/test/java/cucumber/runtime/BackendModuleBackendSupplierTest.java +++ b/core/src/test/java/io/cucumber/core/runtime/BackendServiceLoaderTest.java @@ -1,22 +1,20 @@ -package cucumber.runtime; +package io.cucumber.core.runtime; -import cucumber.runtime.io.MultiLoader; -import cucumber.runtime.io.ResourceLoader; -import cucumber.runtime.io.ResourceLoaderClassFinder; +import io.cucumber.core.backend.ObjectFactoryServiceLoader; +import io.cucumber.core.exception.CucumberException; +import io.cucumber.core.io.MultiLoader; +import io.cucumber.core.io.ResourceLoader; import io.cucumber.core.options.RuntimeOptions; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import java.net.URI; - -import static java.util.Collections.singletonList; +import static java.util.Collections.emptyList; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.assertThat; -public class BackendModuleBackendSupplierTest { - +public class BackendServiceLoaderTest { @Rule public ExpectedException expectedException = ExpectedException.none(); @@ -26,8 +24,9 @@ public void should_create_a_backend() { ClassLoader classLoader = getClass().getClassLoader(); RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); ResourceLoader resourceLoader = new MultiLoader(classLoader); - ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader); - BackendSupplier backendSupplier = new BackendModuleBackendSupplier(resourceLoader, classFinder, runtimeOptions); + ObjectFactoryServiceLoader objectFactoryServiceLoader = new ObjectFactoryServiceLoader(runtimeOptions); + ObjectFactorySupplier objectFactory = new SingletonObjectFactorySupplier(objectFactoryServiceLoader); + BackendSupplier backendSupplier = new BackendServiceLoader(resourceLoader, objectFactory); assertThat(backendSupplier.get().iterator().next(), is(notNullValue())); } @@ -36,11 +35,12 @@ public void should_throw_an_exception_when_no_backend_could_be_found() { ClassLoader classLoader = getClass().getClassLoader(); RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); ResourceLoader resourceLoader = new MultiLoader(classLoader); - ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader); - BackendSupplier backendSupplier = new BackendModuleBackendSupplier(resourceLoader, classFinder, runtimeOptions, singletonList(URI.create("classpath:no/backend/here"))); + ObjectFactoryServiceLoader objectFactoryServiceLoader = new ObjectFactoryServiceLoader(runtimeOptions); + ObjectFactorySupplier objectFactory = new SingletonObjectFactorySupplier(objectFactoryServiceLoader); + BackendServiceLoader backendSupplier = new BackendServiceLoader(resourceLoader, objectFactory); expectedException.expect(CucumberException.class); - assertThat(backendSupplier.get().iterator().next(), is(notNullValue())); + backendSupplier.get(emptyList()).iterator().next(); } } diff --git a/core/src/test/java/cucumber/runtime/ExitStatusTest.java b/core/src/test/java/io/cucumber/core/runtime/ExitStatusTest.java similarity index 65% rename from core/src/test/java/cucumber/runtime/ExitStatusTest.java rename to core/src/test/java/io/cucumber/core/runtime/ExitStatusTest.java index 1d0424667b..bdb0ce85c9 100644 --- a/core/src/test/java/cucumber/runtime/ExitStatusTest.java +++ b/core/src/test/java/io/cucumber/core/runtime/ExitStatusTest.java @@ -1,29 +1,32 @@ -package cucumber.runtime; +package io.cucumber.core.runtime; -import cucumber.api.Result; -import cucumber.api.TestCase; -import cucumber.api.event.TestCaseFinished; -import cucumber.runner.EventBus; -import cucumber.runner.TimeService; -import cucumber.runner.TimeServiceEventBus; -import io.cucumber.core.options.RuntimeOptions; +import io.cucumber.core.event.Result; +import io.cucumber.core.event.Status; +import io.cucumber.core.event.TestCase; +import io.cucumber.core.event.TestCaseFinished; +import io.cucumber.core.eventbus.EventBus; import io.cucumber.core.options.CommandlineOptionsParser; +import io.cucumber.core.options.RuntimeOptions; import org.junit.Test; +import java.time.Clock; +import java.time.Instant; + +import static java.time.Duration.ZERO; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; public class ExitStatusTest { - private final static long ANY_TIMESTAMP = 1234567890; + private final static Instant ANY_INSTANT = Instant.ofEpochMilli(1234567890); private EventBus bus; - private ExitStatus exitStatus; + private Runtime.ExitStatus exitStatus; @Test public void non_strict_wip_with_ambiguous_scenarios() { createNonStrictWipExitStatus(); - bus.send(testCaseFinishedWithStatus(Result.Type.AMBIGUOUS)); + bus.send(testCaseFinishedWithStatus(Status.AMBIGUOUS)); assertEquals(0x0, exitStatus.exitStatus()); } @@ -32,23 +35,23 @@ private void createNonStrictWipExitStatus() { createExitStatus("-g", "anything", "--wip"); } - private TestCaseFinished testCaseFinishedWithStatus(Result.Type resultStatus) { - return new TestCaseFinished(ANY_TIMESTAMP, ANY_TIMESTAMP, mock(TestCase.class), new Result(resultStatus, 0L, null)); + private TestCaseFinished testCaseFinishedWithStatus(Status resultStatus) { + return new TestCaseFinished(ANY_INSTANT, mock(TestCase.class), new Result(resultStatus, ZERO, null)); } private void createExitStatus(String... runtimeArgs) { RuntimeOptions runtimeOptions = new CommandlineOptionsParser() .parse(runtimeArgs) .build(); - this.bus = new TimeServiceEventBus(TimeService.SYSTEM); - exitStatus = new ExitStatus(runtimeOptions); + this.bus = new TimeServiceEventBus(Clock.systemUTC()); + exitStatus = new Runtime.ExitStatus(runtimeOptions); exitStatus.setEventPublisher(bus); } @Test public void non_strict_wip_with_failed_scenarios() { createNonStrictWipExitStatus(); - bus.send(testCaseFinishedWithStatus(Result.Type.FAILED)); + bus.send(testCaseFinishedWithStatus(Status.FAILED)); assertEquals(0x0, exitStatus.exitStatus()); } @@ -56,7 +59,7 @@ public void non_strict_wip_with_failed_scenarios() { @Test public void non_strict_wip_with_passed_scenarios() { createNonStrictWipExitStatus(); - bus.send(testCaseFinishedWithStatus(Result.Type.PASSED)); + bus.send(testCaseFinishedWithStatus(Status.PASSED)); assertEquals(0x1, exitStatus.exitStatus()); } @@ -64,7 +67,7 @@ public void non_strict_wip_with_passed_scenarios() { @Test public void non_strict_wip_with_pending_scenarios() { createNonStrictWipExitStatus(); - bus.send(testCaseFinishedWithStatus(Result.Type.PENDING)); + bus.send(testCaseFinishedWithStatus(Status.PENDING)); assertEquals(0x0, exitStatus.exitStatus()); } @@ -72,7 +75,7 @@ public void non_strict_wip_with_pending_scenarios() { @Test public void non_strict_wip_with_skipped_scenarios() { createNonStrictWipExitStatus(); - bus.send(testCaseFinishedWithStatus(Result.Type.SKIPPED)); + bus.send(testCaseFinishedWithStatus(Status.SKIPPED)); assertEquals(0x0, exitStatus.exitStatus()); } @@ -80,14 +83,14 @@ public void non_strict_wip_with_skipped_scenarios() { @Test public void non_strict_wip_with_undefined_scenarios() { createNonStrictWipExitStatus(); - bus.send(testCaseFinishedWithStatus(Result.Type.UNDEFINED)); + bus.send(testCaseFinishedWithStatus(Status.UNDEFINED)); assertEquals(0x0, exitStatus.exitStatus()); } @Test public void non_strict_with_ambiguous_scenarios() { createNonStrictExitStatus(); - bus.send(testCaseFinishedWithStatus(Result.Type.AMBIGUOUS)); + bus.send(testCaseFinishedWithStatus(Status.AMBIGUOUS)); assertEquals(0x1, exitStatus.exitStatus()); } @@ -99,7 +102,7 @@ private void createNonStrictExitStatus() { @Test public void non_strict_with_failed_scenarios() { createNonStrictExitStatus(); - bus.send(testCaseFinishedWithStatus(Result.Type.FAILED)); + bus.send(testCaseFinishedWithStatus(Status.FAILED)); assertEquals(0x1, exitStatus.exitStatus()); } @@ -107,7 +110,7 @@ public void non_strict_with_failed_scenarios() { @Test public void non_strict_with_passed_scenarios() { createNonStrictExitStatus(); - bus.send(testCaseFinishedWithStatus(Result.Type.PASSED)); + bus.send(testCaseFinishedWithStatus(Status.PASSED)); assertEquals(0x0, exitStatus.exitStatus()); } @@ -115,7 +118,7 @@ public void non_strict_with_passed_scenarios() { @Test public void non_strict_with_pending_scenarios() { createNonStrictExitStatus(); - bus.send(testCaseFinishedWithStatus(Result.Type.PENDING)); + bus.send(testCaseFinishedWithStatus(Status.PENDING)); assertEquals(0x0, exitStatus.exitStatus()); } @@ -123,7 +126,7 @@ public void non_strict_with_pending_scenarios() { @Test public void non_strict_with_skipped_scenarios() { createNonStrictExitStatus(); - bus.send(testCaseFinishedWithStatus(Result.Type.SKIPPED)); + bus.send(testCaseFinishedWithStatus(Status.SKIPPED)); assertEquals(0x0, exitStatus.exitStatus()); } @@ -131,7 +134,7 @@ public void non_strict_with_skipped_scenarios() { @Test public void non_strict_with_undefined_scenarios() { createNonStrictExitStatus(); - bus.send(testCaseFinishedWithStatus(Result.Type.UNDEFINED)); + bus.send(testCaseFinishedWithStatus(Status.UNDEFINED)); assertEquals(0x0, exitStatus.exitStatus()); } @@ -144,7 +147,7 @@ public void should_pass_if_no_features_are_found() { @Test public void strict_wip_with_ambiguous_scenarios() { createStrictWipRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.AMBIGUOUS)); + bus.send(testCaseFinishedWithStatus(Status.AMBIGUOUS)); assertEquals(0x0, exitStatus.exitStatus()); } @@ -156,8 +159,8 @@ private void createStrictWipRuntime() { @Test public void strict_wip_with_failed_failed_scenarios() { createStrictWipRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.FAILED)); - bus.send(testCaseFinishedWithStatus(Result.Type.FAILED)); + bus.send(testCaseFinishedWithStatus(Status.FAILED)); + bus.send(testCaseFinishedWithStatus(Status.FAILED)); assertEquals(0x0, exitStatus.exitStatus()); } @@ -165,8 +168,8 @@ public void strict_wip_with_failed_failed_scenarios() { @Test public void strict_wip_with_failed_passed_scenarios() { createStrictWipRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.PASSED)); - bus.send(testCaseFinishedWithStatus(Result.Type.FAILED)); + bus.send(testCaseFinishedWithStatus(Status.PASSED)); + bus.send(testCaseFinishedWithStatus(Status.FAILED)); assertEquals(0x1, exitStatus.exitStatus()); } @@ -174,7 +177,7 @@ public void strict_wip_with_failed_passed_scenarios() { @Test public void strict_wip_with_failed_scenarios() { createStrictWipRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.FAILED)); + bus.send(testCaseFinishedWithStatus(Status.FAILED)); assertEquals(0x0, exitStatus.exitStatus()); } @@ -182,8 +185,8 @@ public void strict_wip_with_failed_scenarios() { @Test public void strict_wip_with_passed_failed_scenarios() { createStrictWipRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.PASSED)); - bus.send(testCaseFinishedWithStatus(Result.Type.FAILED)); + bus.send(testCaseFinishedWithStatus(Status.PASSED)); + bus.send(testCaseFinishedWithStatus(Status.FAILED)); assertEquals(0x1, exitStatus.exitStatus()); } @@ -191,7 +194,7 @@ public void strict_wip_with_passed_failed_scenarios() { @Test public void strict_wip_with_passed_scenarios() { createStrictWipRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.PASSED)); + bus.send(testCaseFinishedWithStatus(Status.PASSED)); assertEquals(0x1, exitStatus.exitStatus()); } @@ -199,7 +202,7 @@ public void strict_wip_with_passed_scenarios() { @Test public void strict_wip_with_pending_scenarios() { createStrictWipRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.PENDING)); + bus.send(testCaseFinishedWithStatus(Status.PENDING)); assertEquals(0x0, exitStatus.exitStatus()); } @@ -207,7 +210,7 @@ public void strict_wip_with_pending_scenarios() { @Test public void strict_wip_with_skipped_scenarios() { createNonStrictWipExitStatus(); - bus.send(testCaseFinishedWithStatus(Result.Type.SKIPPED)); + bus.send(testCaseFinishedWithStatus(Status.SKIPPED)); assertEquals(0x0, exitStatus.exitStatus()); } @@ -215,14 +218,14 @@ public void strict_wip_with_skipped_scenarios() { @Test public void strict_wip_with_undefined_scenarios() { createStrictWipRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.UNDEFINED)); + bus.send(testCaseFinishedWithStatus(Status.UNDEFINED)); assertEquals(0x0, exitStatus.exitStatus()); } @Test public void strict_with_ambiguous_scenarios() { createStrictRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.AMBIGUOUS)); + bus.send(testCaseFinishedWithStatus(Status.AMBIGUOUS)); assertEquals(0x1, exitStatus.exitStatus()); } @@ -234,8 +237,8 @@ private void createStrictRuntime() { @Test public void strict_with_failed_failed_scenarios() { createStrictRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.FAILED)); - bus.send(testCaseFinishedWithStatus(Result.Type.FAILED)); + bus.send(testCaseFinishedWithStatus(Status.FAILED)); + bus.send(testCaseFinishedWithStatus(Status.FAILED)); assertEquals(0x1, exitStatus.exitStatus()); } @@ -243,8 +246,8 @@ public void strict_with_failed_failed_scenarios() { @Test public void strict_with_failed_passed_scenarios() { createStrictRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.FAILED)); - bus.send(testCaseFinishedWithStatus(Result.Type.PASSED)); + bus.send(testCaseFinishedWithStatus(Status.FAILED)); + bus.send(testCaseFinishedWithStatus(Status.PASSED)); assertEquals(0x1, exitStatus.exitStatus()); } @@ -252,7 +255,7 @@ public void strict_with_failed_passed_scenarios() { @Test public void strict_with_failed_scenarios() { createStrictRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.FAILED)); + bus.send(testCaseFinishedWithStatus(Status.FAILED)); assertEquals(0x1, exitStatus.exitStatus()); } @@ -260,8 +263,8 @@ public void strict_with_failed_scenarios() { @Test public void strict_with_passed_failed_scenarios() { createStrictRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.PASSED)); - bus.send(testCaseFinishedWithStatus(Result.Type.FAILED)); + bus.send(testCaseFinishedWithStatus(Status.PASSED)); + bus.send(testCaseFinishedWithStatus(Status.FAILED)); assertEquals(0x1, exitStatus.exitStatus()); } @@ -269,8 +272,8 @@ public void strict_with_passed_failed_scenarios() { @Test public void strict_with_passed_passed_scenarios() { createStrictRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.PASSED)); - bus.send(testCaseFinishedWithStatus(Result.Type.PASSED)); + bus.send(testCaseFinishedWithStatus(Status.PASSED)); + bus.send(testCaseFinishedWithStatus(Status.PASSED)); assertEquals(0x0, exitStatus.exitStatus()); } @@ -278,7 +281,7 @@ public void strict_with_passed_passed_scenarios() { @Test public void strict_with_passed_scenarios() { createStrictRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.PASSED)); + bus.send(testCaseFinishedWithStatus(Status.PASSED)); assertEquals(0x0, exitStatus.exitStatus()); } @@ -286,7 +289,7 @@ public void strict_with_passed_scenarios() { @Test public void strict_with_pending_scenarios() { createStrictRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.PENDING)); + bus.send(testCaseFinishedWithStatus(Status.PENDING)); assertEquals(0x1, exitStatus.exitStatus()); } @@ -294,7 +297,7 @@ public void strict_with_pending_scenarios() { @Test public void strict_with_skipped_scenarios() { createNonStrictExitStatus(); - bus.send(testCaseFinishedWithStatus(Result.Type.SKIPPED)); + bus.send(testCaseFinishedWithStatus(Status.SKIPPED)); assertEquals(0x0, exitStatus.exitStatus()); } @@ -302,7 +305,7 @@ public void strict_with_skipped_scenarios() { @Test public void strict_with_undefined_scenarios() { createStrictRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.UNDEFINED)); + bus.send(testCaseFinishedWithStatus(Status.UNDEFINED)); assertEquals(0x1, exitStatus.exitStatus()); } diff --git a/core/src/test/java/cucumber/runtime/FeaturePathFeatureSupplierTest.java b/core/src/test/java/io/cucumber/core/runtime/FeaturePathFeatureSupplierTest.java similarity index 68% rename from core/src/test/java/cucumber/runtime/FeaturePathFeatureSupplierTest.java rename to core/src/test/java/io/cucumber/core/runtime/FeaturePathFeatureSupplierTest.java index 850dfa0aa1..8700039b88 100644 --- a/core/src/test/java/cucumber/runtime/FeaturePathFeatureSupplierTest.java +++ b/core/src/test/java/io/cucumber/core/runtime/FeaturePathFeatureSupplierTest.java @@ -1,19 +1,17 @@ -package cucumber.runtime; +package io.cucumber.core.runtime; -import cucumber.runtime.io.Resource; -import cucumber.runtime.io.ResourceLoader; -import cucumber.runtime.model.FeatureLoader; +import io.cucumber.core.io.ResourceLoader; import io.cucumber.core.logging.LogRecordListener; import io.cucumber.core.logging.LoggerFactory; -import io.cucumber.core.model.FeaturePath; -import io.cucumber.core.options.FeatureOptions; +import io.cucumber.core.feature.FeatureLoader; +import io.cucumber.core.feature.FeaturePath; +import io.cucumber.core.feature.Options; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.net.URI; import java.util.Collections; -import java.util.List; import static org.hamcrest.CoreMatchers.containsString; import static org.junit.Assert.assertThat; @@ -38,13 +36,8 @@ public void tearDown(){ @Test public void logs_message_if_no_features_are_found() { ResourceLoader resourceLoader = mock(ResourceLoader.class); - when(resourceLoader.resources(URI.create("file:does/not/exist"), ".feature")).thenReturn(Collections.emptyList()); - FeatureOptions featureOptions = new FeatureOptions() { - @Override - public List getFeaturePaths() { - return Collections.singletonList(FeaturePath.parse("does/not/exist")); - } - }; + when(resourceLoader.resources(URI.create("file:does/not/exist"), ".feature")).thenReturn(Collections.emptyList()); + Options featureOptions = () -> Collections.singletonList(FeaturePath.parse("does/not/exist")); FeaturePathFeatureSupplier supplier = new FeaturePathFeatureSupplier(new FeatureLoader(resourceLoader), featureOptions); supplier.get(); @@ -54,12 +47,7 @@ public List getFeaturePaths() { @Test public void logs_message_if_no_feature_paths_are_given() { ResourceLoader resourceLoader = mock(ResourceLoader.class); - FeatureOptions featureOptions = new FeatureOptions() { - @Override - public List getFeaturePaths() { - return Collections.emptyList(); - } - }; + Options featureOptions = Collections::emptyList; FeaturePathFeatureSupplier supplier = new FeaturePathFeatureSupplier(new FeatureLoader(resourceLoader), featureOptions); supplier.get(); diff --git a/core/src/test/java/cucumber/runtime/TimeoutTest.java b/core/src/test/java/io/cucumber/core/runtime/InvokerTest.java similarity index 56% rename from core/src/test/java/cucumber/runtime/TimeoutTest.java rename to core/src/test/java/io/cucumber/core/runtime/InvokerTest.java index edc127a567..a79804f5af 100644 --- a/core/src/test/java/cucumber/runtime/TimeoutTest.java +++ b/core/src/test/java/io/cucumber/core/runtime/InvokerTest.java @@ -1,21 +1,25 @@ -package cucumber.runtime; +package io.cucumber.core.runtime; import org.junit.Test; +import org.junit.jupiter.api.function.Executable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeoutException; import static java.lang.Thread.sleep; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class InvokerTest { -public class TimeoutTest { @Test public void doesnt_time_out_if_it_doesnt_take_too_long() throws Throwable { final Slow slow = new Slow(); - String what = Timeout.timeout(new Timeout.Callback() { + String what = Invoker.timeout(new Invoker.Callback() { @Override public String call() throws Throwable { return slow.slow(10); @@ -24,55 +28,46 @@ public String call() throws Throwable { assertEquals("slept 10ms", what); } - @Test(expected = TimeoutException.class) - public void times_out_if_it_takes_too_long() throws Throwable { + @Test + public void times_out_if_it_takes_too_long() { final Slow slow = new Slow(); - Timeout.timeout(new Timeout.Callback() { - @Override - public String call() throws Throwable { - return slow.slow(100); - } - }, 50); - fail(); + final Executable testMethod = () -> Invoker.timeout(() -> slow.slow(100), 50); + final TimeoutException expectedThrown = assertThrows(TimeoutException.class, testMethod); + assertThat(expectedThrown.getMessage(), is(equalTo("Timed out after 50ms."))); } - @Test(expected = TimeoutException.class) - public void times_out_infinite_loop_if_it_takes_too_long() throws Throwable { + @Test + public void times_out_infinite_loop_if_it_takes_too_long() { final Slow slow = new Slow(); - Timeout.timeout(new Timeout.Callback() { - @Override - public Void call() throws Throwable { - slow.infinite(); - return null; - } + final Executable testMethod = () -> Invoker.timeout((Invoker.Callback) () -> { + slow.infinite(); + return null; }, 10); - fail(); + final TimeoutException expectedThrown = assertThrows(TimeoutException.class, testMethod); + assertThat(expectedThrown.getMessage(), is(equalTo("Timed out after 10ms."))); } - @Test(expected = TimeoutException.class) - public void times_out_infinite_latch_wait_if_it_takes_too_long() throws Throwable { + @Test + public void times_out_infinite_latch_wait_if_it_takes_too_long() { final Slow slow = new Slow(); - Timeout.timeout(new Timeout.Callback() { - @Override - public Void call() throws Throwable { - slow.infiniteLatchWait(); - return null; - } + final Executable testMethod = () -> Invoker.timeout((Invoker.Callback) () -> { + slow.infiniteLatchWait(); + return null; }, 10); - fail(); + final TimeoutException expectedThrown = assertThrows(TimeoutException.class, testMethod); + assertThat(expectedThrown.getMessage(), is(equalTo("Timed out after 10ms."))); } - @Test(expected = TimeoutException.class) - public void times_out_busy_wait_if_it_takes_too_long() throws Throwable { + @Test + public void times_out_busy_wait_if_it_takes_too_long() { final Slow slow = new Slow(); - Timeout.timeout(new Timeout.Callback() { - @Override - public Void call() throws Throwable { - slow.busyWait(); - return null; - } + final Executable testMethod = () -> Invoker.timeout((Invoker.Callback) () -> { + slow.busyWait(); + return null; }, 1); + final TimeoutException expectedThrown = assertThrows(TimeoutException.class, testMethod); + assertThat(expectedThrown.getMessage(), is(equalTo("Timed out after 1ms."))); } @Test @@ -83,7 +78,7 @@ public void doesnt_leak_threads() throws Throwable { boolean cleanedUp = false; for (int i = 0; i < 1000; i++) { - Timeout.timeout(new Timeout.Callback() { + Invoker.timeout(new Invoker.Callback() { @Override public String call() throws Throwable { return null; @@ -127,4 +122,5 @@ public int busyWait() throws InterruptedException { return busyCounter; } } + } diff --git a/core/src/test/java/cucumber/runtime/RuntimeTest.java b/core/src/test/java/io/cucumber/core/runtime/RuntimeTest.java similarity index 79% rename from core/src/test/java/cucumber/runtime/RuntimeTest.java rename to core/src/test/java/io/cucumber/core/runtime/RuntimeTest.java index e2e5cf3acf..3c2a56e9ea 100644 --- a/core/src/test/java/cucumber/runtime/RuntimeTest.java +++ b/core/src/test/java/io/cucumber/core/runtime/RuntimeTest.java @@ -1,37 +1,39 @@ -package cucumber.runtime; - -import cucumber.api.HookType; -import cucumber.api.Plugin; -import cucumber.api.Result; -import cucumber.api.Scenario; -import cucumber.api.StepDefinitionReporter; -import cucumber.api.TestCase; -import cucumber.api.event.ConcurrentEventListener; -import cucumber.api.event.EventHandler; -import cucumber.api.event.EventListener; -import cucumber.api.event.EventPublisher; -import cucumber.api.event.StepDefinedEvent; -import cucumber.api.event.TestCaseFinished; -import cucumber.api.event.TestStepFinished; -import cucumber.runner.EventBus; -import cucumber.runner.TestBackendSupplier; -import cucumber.runner.TestHelper; -import cucumber.runner.TimeService; -import cucumber.runner.TimeServiceEventBus; -import cucumber.runner.TimeServiceStub; -import cucumber.runtime.formatter.FormatterBuilder; -import cucumber.runtime.formatter.FormatterSpy; -import cucumber.runtime.io.ClasspathResourceLoader; -import cucumber.runtime.io.Resource; -import cucumber.runtime.io.ResourceLoader; -import cucumber.runtime.model.CucumberFeature; +package io.cucumber.core.runtime; + import gherkin.ast.ScenarioDefinition; import gherkin.ast.Step; -import gherkin.pickles.PickleStep; import gherkin.pickles.PickleTag; +import io.cucumber.core.api.Scenario; +import io.cucumber.core.backend.Glue; +import io.cucumber.core.backend.HookDefinition; +import io.cucumber.core.backend.ParameterInfo; +import io.cucumber.core.event.EventHandler; +import io.cucumber.core.event.EventPublisher; +import io.cucumber.core.event.HookType; +import io.cucumber.core.event.Result; +import io.cucumber.core.event.Status; +import io.cucumber.core.event.StepDefinedEvent; +import io.cucumber.core.event.StepDefinition; +import io.cucumber.core.event.TestCase; +import io.cucumber.core.event.TestCaseFinished; +import io.cucumber.core.event.TestStepFinished; +import io.cucumber.core.eventbus.EventBus; +import io.cucumber.core.exception.CompositeCucumberException; +import io.cucumber.core.feature.CucumberFeature; +import io.cucumber.core.io.Resource; +import io.cucumber.core.io.ResourceLoader; +import io.cucumber.core.io.TestClasspathResourceLoader; import io.cucumber.core.options.CommandlineOptionsParser; -import io.cucumber.stepexpression.Argument; -import io.cucumber.stepexpression.TypeRegistry; +import io.cucumber.core.plugin.ConcurrentEventListener; +import io.cucumber.core.plugin.EventListener; +import io.cucumber.core.plugin.FormatterBuilder; +import io.cucumber.core.plugin.FormatterSpy; +import io.cucumber.core.plugin.Plugin; +import io.cucumber.core.runner.ScenarioScoped; +import io.cucumber.core.runner.StepDurationTimeService; +import io.cucumber.core.runner.TestBackendSupplier; +import io.cucumber.core.runner.TestHelper; +import io.cucumber.core.stepexpression.TypeRegistry; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -39,6 +41,9 @@ import org.mockito.ArgumentMatchers; import java.net.URI; +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -47,17 +52,16 @@ import java.util.Locale; import java.util.Map; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicBoolean; -import static cucumber.runner.TestHelper.feature; -import static cucumber.runner.TestHelper.result; +import static io.cucumber.core.runner.TestHelper.feature; +import static io.cucumber.core.runner.TestHelper.result; +import static java.time.Duration.ZERO; +import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static java.util.concurrent.TimeUnit.HOURS; -import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.collection.IsIterableContainingInOrder.contains; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -68,10 +72,10 @@ import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs; public class RuntimeTest { - private final static long ANY_TIMESTAMP = 1234567890; + private final static Instant ANY_INSTANT = Instant.ofEpochMilli(1234567890); private final TypeRegistry TYPE_REGISTRY = new TypeRegistry(Locale.ENGLISH); - private final EventBus bus = new TimeServiceEventBus(TimeService.SYSTEM); + private final EventBus bus = new TimeServiceEventBus(Clock.systemUTC()); @Rule public ExpectedException expectedException = ExpectedException.none(); @@ -94,17 +98,12 @@ public void loadGlue(Glue glue, List gluePaths) { } }; - FeatureSupplier featureSupplier = new FeatureSupplier() { - @Override - public List get() { - return singletonList(feature); - } - }; + FeatureSupplier featureSupplier = new TestFeatureSupplier(bus, feature); Runtime.builder() .withBackendSupplier(backendSupplier) .withAdditionalPlugins(jsonFormatter) - .withResourceLoader(new ClasspathResourceLoader(classLoader)) - .withEventBus(new TimeServiceEventBus(new TimeServiceStub(0))) + .withResourceLoader(TestClasspathResourceLoader.create(classLoader)) + .withEventBus(new TimeServiceEventBus(Clock.fixed(Instant.EPOCH, ZoneId.of("UTC")))) .withFeatureSupplier(featureSupplier) .build() .run(); @@ -167,7 +166,7 @@ public List get() { @Test public void strict_with_passed_scenarios() { Runtime runtime = createStrictRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.PASSED)); + bus.send(testCaseFinishedWithStatus(Status.PASSED)); assertEquals(0x0, runtime.exitStatus()); } @@ -175,7 +174,7 @@ public void strict_with_passed_scenarios() { @Test public void non_strict_with_passed_scenarios() { Runtime runtime = createNonStrictRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.PASSED)); + bus.send(testCaseFinishedWithStatus(Status.PASSED)); assertEquals(0x0, runtime.exitStatus()); } @@ -183,21 +182,21 @@ public void non_strict_with_passed_scenarios() { @Test public void non_strict_with_undefined_scenarios() { Runtime runtime = createNonStrictRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.UNDEFINED)); + bus.send(testCaseFinishedWithStatus(Status.UNDEFINED)); assertEquals(0x0, runtime.exitStatus()); } @Test public void strict_with_undefined_scenarios() { Runtime runtime = createStrictRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.UNDEFINED)); + bus.send(testCaseFinishedWithStatus(Status.UNDEFINED)); assertEquals(0x1, runtime.exitStatus()); } @Test public void strict_with_pending_scenarios() { Runtime runtime = createStrictRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.PENDING)); + bus.send(testCaseFinishedWithStatus(Status.PENDING)); assertEquals(0x1, runtime.exitStatus()); } @@ -205,7 +204,7 @@ public void strict_with_pending_scenarios() { @Test public void non_strict_with_pending_scenarios() { Runtime runtime = createNonStrictRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.PENDING)); + bus.send(testCaseFinishedWithStatus(Status.PENDING)); assertEquals(0x0, runtime.exitStatus()); } @@ -213,7 +212,7 @@ public void non_strict_with_pending_scenarios() { @Test public void non_strict_with_skipped_scenarios() { Runtime runtime = createNonStrictRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.SKIPPED)); + bus.send(testCaseFinishedWithStatus(Status.SKIPPED)); assertEquals(0x0, runtime.exitStatus()); } @@ -221,7 +220,7 @@ public void non_strict_with_skipped_scenarios() { @Test public void strict_with_skipped_scenarios() { Runtime runtime = createNonStrictRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.SKIPPED)); + bus.send(testCaseFinishedWithStatus(Status.SKIPPED)); assertEquals(0x0, runtime.exitStatus()); } @@ -229,7 +228,7 @@ public void strict_with_skipped_scenarios() { @Test public void non_strict_with_failed_scenarios() { Runtime runtime = createNonStrictRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.FAILED)); + bus.send(testCaseFinishedWithStatus(Status.FAILED)); assertEquals(0x1, runtime.exitStatus()); } @@ -237,7 +236,7 @@ public void non_strict_with_failed_scenarios() { @Test public void strict_with_failed_scenarios() { Runtime runtime = createStrictRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.FAILED)); + bus.send(testCaseFinishedWithStatus(Status.FAILED)); assertEquals(0x1, runtime.exitStatus()); } @@ -245,7 +244,7 @@ public void strict_with_failed_scenarios() { @Test public void non_strict_with_ambiguous_scenarios() { Runtime runtime = createNonStrictRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.AMBIGUOUS)); + bus.send(testCaseFinishedWithStatus(Status.AMBIGUOUS)); assertEquals(0x1, runtime.exitStatus()); } @@ -253,7 +252,7 @@ public void non_strict_with_ambiguous_scenarios() { @Test public void strict_with_ambiguous_scenarios() { Runtime runtime = createStrictRuntime(); - bus.send(testCaseFinishedWithStatus(Result.Type.AMBIGUOUS)); + bus.send(testCaseFinishedWithStatus(Status.AMBIGUOUS)); assertEquals(0x1, runtime.exitStatus()); } @@ -268,57 +267,26 @@ public void should_pass_if_no_features_are_found() { assertEquals(0x0, runtime.exitStatus()); } - @Test - public void reports_step_definitions_to_plugin() { - ResourceLoader resourceLoader = mock(ResourceLoader.class); - final StubStepDefinition stepDefinition = new StubStepDefinition("some pattern", new TypeRegistry(Locale.ENGLISH)); - TestBackendSupplier testBackendSupplier = new TestBackendSupplier() { - @Override - public void loadGlue(Glue glue, List gluePaths) { - glue.addStepDefinition(stepDefinition); - } - }; - - Runtime.builder() - .withResourceLoader(resourceLoader) - .withRuntimeOptions( - new CommandlineOptionsParser() - .parse("--plugin", "cucumber.runtime.RuntimeTest$StepdefsPrinter") - .build() - ) - .withBackendSupplier(testBackendSupplier) - .build() - .run(); - - assertSame(stepDefinition, StepdefsPrinter.instance.stepDefinition); - } - - public static class StepdefsPrinter implements StepDefinitionReporter { - static StepdefsPrinter instance; - StepDefinition stepDefinition; - - public StepdefsPrinter() { - instance = this; - } - - @Override - public void stepDefinition(StepDefinition stepDefinition) { - this.stepDefinition = stepDefinition; - } - } - @Test public void should_make_scenario_name_available_to_hooks() throws Throwable { - CucumberFeature feature = TestHelper.feature("path/test.feature", + final CucumberFeature feature = TestHelper.feature("path/test.feature", "Feature: feature name\n" + " Scenario: scenario name\n" + " Given first step\n" + " When second step\n" + " Then third step\n"); - HookDefinition beforeHook = mock(HookDefinition.class); + final HookDefinition beforeHook = mock(HookDefinition.class); when(beforeHook.matches(ArgumentMatchers.anyCollection())).thenReturn(true); - Runtime runtime = createRuntimeWithMockedGlue(beforeHook, HookType.Before, feature); + TestBackendSupplier testBackendSupplier = createTestBackendSupplier(feature, beforeHook); + + FeatureSupplier featureSupplier = new TestFeatureSupplier(bus, feature); + + Runtime runtime = Runtime.builder() + .withBackendSupplier(testBackendSupplier) + .withFeatureSupplier(featureSupplier) + .withEventBus(bus) + .build(); runtime.run(); ArgumentCaptor capturedScenario = ArgumentCaptor.forClass(Scenario.class); @@ -326,6 +294,20 @@ public void should_make_scenario_name_available_to_hooks() throws Throwable { assertEquals("scenario name", capturedScenario.getValue().getName()); } + private TestBackendSupplier createTestBackendSupplier(final CucumberFeature feature, final HookDefinition beforeHook) { + return new TestBackendSupplier() { + @Override + public void loadGlue(Glue glue, List gluePaths) { + for (ScenarioDefinition child : feature.getGherkinFeature().getFeature().getChildren()) { + for (Step step : child.getSteps()) { + mockMatch(glue, step.getText()); + } + } + mockHook(glue, beforeHook, HookType.BEFORE); + } + }; + } + @Test public void should_call_formatter_for_two_scenarios_with_background() { CucumberFeature feature = TestHelper.feature("path/test.feature", "" + @@ -432,18 +414,19 @@ public void should_call_formatter_with_correct_sequence_of_events_when_running_i " Scenario: scenario_3 name\n" + " Given first step\n"); - Map stepsToResult = new HashMap<>(); - stepsToResult.put("first step", result("passed")); - FormatterSpy formatterSpy = new FormatterSpy(); final List features = Arrays.asList(feature1, feature2, feature3); - TestHelper.builder() - .withFeatures(features) - .withStepsToResult(stepsToResult) - .withFormatterUnderTest(formatterSpy) - .withTimeServiceType(TestHelper.TimeServiceType.REAL_TIME) - .withRuntimeArgs("--threads", String.valueOf(features.size())) + Runtime.builder() + .withFeatureSupplier(new TestFeatureSupplier(bus, features)) + .withEventBus(bus) + .withRuntimeOptions( + new CommandlineOptionsParser() + .parse("--threads", String.valueOf(features.size())) + .build() + ) + .withAdditionalPlugins(formatterSpy) + .withBackendSupplier(new TestHelper.TestHelperBackendSupplier(features)) .build() .run(); @@ -541,6 +524,7 @@ public void receive(TestStepFinished event) { }); } }; + Thread thread = new Thread(new Runnable() { @Override public void run() { @@ -554,6 +538,7 @@ public void run() { } }); + thread.start(); threadBlocked.await(1, SECONDS); thread.interrupt(); @@ -581,14 +566,15 @@ public void setEventPublisher(EventPublisher publisher) { publisher.registerHandlerFor(StepDefinedEvent.class, new EventHandler() { @Override public void receive(StepDefinedEvent event) { - stepDefinedEvents.add(event.stepDefinition); + stepDefinedEvents.add(event.getStepDefinition()); } }); } }; - final List definedStepDefinitions = new ArrayList<>(); + final MockedStepDefinition mockedStepDefinition = new MockedStepDefinition(); + final MockedScenarioScopedStepDefinition mockedScenarioScopedStepDefinition = new MockedScenarioScopedStepDefinition(); BackendSupplier backendSupplier = new TestBackendSupplier() { @@ -597,15 +583,11 @@ public void receive(StepDefinedEvent event) { @Override public void loadGlue(Glue glue, List gluePaths) { this.glue = glue; - final StepDefinition mockedStepDefinition = new MockedStepDefinition(); - definedStepDefinitions.add(mockedStepDefinition); glue.addStepDefinition(mockedStepDefinition); } @Override public void buildWorld() { - final StepDefinition mockedScenarioScopedStepDefinition = new MockedScenarioScopedStepDefinition(); - definedStepDefinitions.add(mockedScenarioScopedStepDefinition); glue.addStepDefinition(mockedScenarioScopedStepDefinition); } }; @@ -621,13 +603,20 @@ public List get() { Runtime.builder() .withBackendSupplier(backendSupplier) .withAdditionalPlugins(eventListener) - .withResourceLoader(new ClasspathResourceLoader(classLoader)) - .withEventBus(new TimeServiceEventBus(new TimeServiceStub(0))) + .withResourceLoader(TestClasspathResourceLoader.create(classLoader)) + .withEventBus(new TimeServiceEventBus(new StepDurationTimeService(ZERO))) .withFeatureSupplier(featureSupplier) .build() .run(); - assertThat(stepDefinedEvents, equalTo(definedStepDefinitions)); + + assertThat(stepDefinedEvents, contains( + mockedStepDefinition, + mockedScenarioScopedStepDefinition, + // Twice, once for each scenario + mockedStepDefinition, + mockedScenarioScopedStepDefinition + )); for (StepDefinition stepDefinedEvent : stepDefinedEvents) { if (stepDefinedEvent instanceof MockedScenarioScopedStepDefinition) { @@ -733,36 +722,34 @@ public List get() { } private void mockMatch(Glue glue, String text) { - StepDefinition stepDefinition = new StubStepDefinition(text, TYPE_REGISTRY); + io.cucumber.core.backend.StepDefinition stepDefinition = new StubStepDefinition(text); glue.addStepDefinition(stepDefinition); } private void mockHook(Glue glue, HookDefinition hook, HookType hookType) { switch (hookType) { - case Before: + case BEFORE: glue.addBeforeHook(hook); return; - case After: + case AFTER: glue.addAfterHook(hook); return; - case AfterStep: - glue.addBeforeHook(hook); + case AFTER_STEP: + glue.addAfterStepHook(hook); + return; + case BEFORE_STEP: + glue.addBeforeStepHook(hook); return; default: throw new IllegalArgumentException(hookType.name()); } } - private TestCaseFinished testCaseFinishedWithStatus(Result.Type resultStatus) { - return new TestCaseFinished(ANY_TIMESTAMP, ANY_TIMESTAMP, mock(TestCase.class), new Result(resultStatus, 0L, null)); + private TestCaseFinished testCaseFinishedWithStatus(Status resultStatus) { + return new TestCaseFinished(ANY_INSTANT, mock(TestCase.class), new Result(resultStatus, ZERO, null)); } - private static final class MockedStepDefinition implements StepDefinition { - - @Override - public List matchedArguments(PickleStep step) { - return step.getText().equals(getPattern()) ? new ArrayList() : null; - } + private static final class MockedStepDefinition implements io.cucumber.core.backend.StepDefinition { @Override public String getLocation(boolean detail) { @@ -770,12 +757,12 @@ public String getLocation(boolean detail) { } @Override - public Integer getParameterCount() { - return 0; + public List parameterInfos() { + return emptyList(); } @Override - public void execute(Object[] args) throws Throwable { + public void execute(Object[] args) { } @@ -789,13 +776,9 @@ public String getPattern() { return "global scoped"; } - @Override - public boolean isScenarioScoped() { - return true; - } } - private static final class MockedScenarioScopedStepDefinition implements StepDefinition, ScenarioScoped { + private static final class MockedScenarioScopedStepDefinition implements io.cucumber.core.backend.StepDefinition, ScenarioScoped { boolean disposed; @@ -804,19 +787,14 @@ public void disposeScenarioScope() { this.disposed = true; } - @Override - public List matchedArguments(PickleStep step) { - return step.getText().equals(getPattern()) ? new ArrayList() : null; - } - @Override public String getLocation(boolean detail) { return "mocked scenario scoped step definition"; } @Override - public Integer getParameterCount() { - return 0; + public List parameterInfos() { + return emptyList(); } @Override @@ -834,9 +812,5 @@ public String getPattern() { return "scenario scoped"; } - @Override - public boolean isScenarioScoped() { - return true; - } } } diff --git a/core/src/test/java/cucumber/runner/SingletonRunnerSupplierTest.java b/core/src/test/java/io/cucumber/core/runtime/SingletonRunnerSupplierTest.java similarity index 52% rename from core/src/test/java/cucumber/runner/SingletonRunnerSupplierTest.java rename to core/src/test/java/io/cucumber/core/runtime/SingletonRunnerSupplierTest.java index 0b166f8c75..62f75ddf1e 100644 --- a/core/src/test/java/cucumber/runner/SingletonRunnerSupplierTest.java +++ b/core/src/test/java/io/cucumber/core/runtime/SingletonRunnerSupplierTest.java @@ -1,15 +1,18 @@ -package cucumber.runner; +package io.cucumber.core.runtime; -import cucumber.runtime.BackendModuleBackendSupplier; -import cucumber.runtime.ClassFinder; +import io.cucumber.core.backend.ObjectFactoryServiceLoader; +import io.cucumber.core.eventbus.EventBus; +import io.cucumber.core.io.ClassFinder; +import io.cucumber.core.io.MultiLoader; +import io.cucumber.core.io.ResourceLoader; +import io.cucumber.core.io.ResourceLoaderClassFinder; import io.cucumber.core.options.RuntimeOptions; -import cucumber.runtime.io.MultiLoader; -import cucumber.runtime.io.ResourceLoader; -import cucumber.runtime.io.ResourceLoaderClassFinder; import org.junit.Before; import org.junit.Test; +import java.time.Clock; + import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.assertSame; @@ -23,12 +26,15 @@ public class SingletonRunnerSupplierTest { @Before public void before() { ClassLoader classLoader = getClass().getClassLoader(); - RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); ResourceLoader resourceLoader = new MultiLoader(classLoader); + RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader); - BackendModuleBackendSupplier backendSupplier = new BackendModuleBackendSupplier(resourceLoader, classFinder, runtimeOptions); - EventBus eventBus = new TimeServiceEventBus(TimeService.SYSTEM); - runnerSupplier = new SingletonRunnerSupplier(runtimeOptions, eventBus, backendSupplier); + ObjectFactoryServiceLoader objectFactoryServiceLoader = new ObjectFactoryServiceLoader(runtimeOptions); + ObjectFactorySupplier objectFactory = new SingletonObjectFactorySupplier(objectFactoryServiceLoader); + BackendServiceLoader backendSupplier = new BackendServiceLoader(resourceLoader, objectFactory); + EventBus eventBus = new TimeServiceEventBus(Clock.systemUTC()); + TypeRegistryConfigurerSupplier typeRegistryConfigurerSupplier = new ScanningTypeRegistryConfigurerSupplier(classFinder, runtimeOptions); + runnerSupplier = new SingletonRunnerSupplier(runtimeOptions, eventBus, backendSupplier, objectFactory, typeRegistryConfigurerSupplier); } @Test diff --git a/core/src/test/java/io/cucumber/core/runtime/StubStepDefinition.java b/core/src/test/java/io/cucumber/core/runtime/StubStepDefinition.java new file mode 100644 index 0000000000..0704144230 --- /dev/null +++ b/core/src/test/java/io/cucumber/core/runtime/StubStepDefinition.java @@ -0,0 +1,75 @@ +package io.cucumber.core.runtime; + +import io.cucumber.core.backend.ParameterInfo; +import io.cucumber.core.backend.StepDefinition; +import io.cucumber.core.backend.TypeResolver; + +import java.lang.reflect.Type; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.junit.Assert.assertEquals; + +public class StubStepDefinition implements StepDefinition { + private final List parameterInfos; + private final String expression; + + public StubStepDefinition(String pattern, Type... types) { + this.parameterInfos = Stream.of(types).map(StubParameterInfo::new).collect(Collectors.toList()); + this.expression = pattern; + } + + @Override + public String getLocation(boolean detail) { + return "{stubbed location" + (detail ? " with details" : "") + "}"; + } + + @Override + public void execute(Object[] args) { + assertEquals(parameterInfos.size(), args.length); + for (int i = 0; i < args.length; i++) { + assertEquals(parameterInfos.get(i).getType(), args[i].getClass()); + } + } + + @Override + public boolean isDefinedAt(StackTraceElement stackTraceElement) { + return false; + } + + @Override + public List parameterInfos() { + return parameterInfos; + } + + @Override + public String getPattern() { + return expression; + } + + private final class StubParameterInfo implements ParameterInfo { + + private final Type type; + + private StubParameterInfo(Type type) { + this.type = type; + } + + @Override + public Type getType() { + return type; + } + + @Override + public boolean isTransposed() { + return false; + } + + @Override + public TypeResolver getTypeResolver() { + return () -> type; + } + } + +} diff --git a/core/src/test/java/io/cucumber/core/runtime/TestFeatureSupplier.java b/core/src/test/java/io/cucumber/core/runtime/TestFeatureSupplier.java new file mode 100644 index 0000000000..45b4ebe1cc --- /dev/null +++ b/core/src/test/java/io/cucumber/core/runtime/TestFeatureSupplier.java @@ -0,0 +1,30 @@ +package io.cucumber.core.runtime; + +import io.cucumber.core.event.TestSourceRead; +import io.cucumber.core.feature.CucumberFeature; +import io.cucumber.core.eventbus.EventBus; + +import java.util.Arrays; +import java.util.List; + +public class TestFeatureSupplier implements FeatureSupplier { + private final EventBus bus ; + private final List features; + + public TestFeatureSupplier(EventBus bus, CucumberFeature... features) { + this(bus, Arrays.asList(features)); + } + + public TestFeatureSupplier(EventBus bus, List features) { + this.bus = bus; + this.features = features; + } + + @Override + public List get() { + for (CucumberFeature feature : features) { + bus.send(new TestSourceRead(bus.getInstant(), feature.getUri().toString(), feature.getSource())); + } + return features; + } +} diff --git a/core/src/test/java/cucumber/runtime/ThreadLocalRunnerSupplierTest.java b/core/src/test/java/io/cucumber/core/runtime/ThreadLocalRunnerSupplierTest.java similarity index 66% rename from core/src/test/java/cucumber/runtime/ThreadLocalRunnerSupplierTest.java rename to core/src/test/java/io/cucumber/core/runtime/ThreadLocalRunnerSupplierTest.java index 86af0f9c01..063f3b42a9 100644 --- a/core/src/test/java/cucumber/runtime/ThreadLocalRunnerSupplierTest.java +++ b/core/src/test/java/io/cucumber/core/runtime/ThreadLocalRunnerSupplierTest.java @@ -1,26 +1,30 @@ -package cucumber.runtime; - - -import cucumber.api.event.EventHandler; -import cucumber.api.event.TestCaseStarted; -import cucumber.runner.EventBus; -import cucumber.runner.Runner; -import cucumber.runner.ThreadLocalRunnerSupplier; -import cucumber.runner.TimeService; -import cucumber.runner.TimeServiceEventBus; -import cucumber.runtime.io.MultiLoader; -import cucumber.runtime.io.ResourceLoader; -import cucumber.runtime.io.ResourceLoaderClassFinder; +package io.cucumber.core.runtime; + + +import io.cucumber.core.backend.ObjectFactoryServiceLoader; +import io.cucumber.core.event.EventHandler; +import io.cucumber.core.event.TestCase; +import io.cucumber.core.event.TestCaseStarted; +import io.cucumber.core.eventbus.EventBus; +import io.cucumber.core.io.ClassFinder; +import io.cucumber.core.io.MultiLoader; +import io.cucumber.core.io.ResourceLoader; +import io.cucumber.core.io.ResourceLoaderClassFinder; import io.cucumber.core.options.RuntimeOptions; +import io.cucumber.core.runner.Runner; import org.junit.Before; import org.junit.Test; +import java.time.Clock; + +import static java.time.Instant.EPOCH; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; public class ThreadLocalRunnerSupplierTest { @@ -34,9 +38,12 @@ public void before() { RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); ResourceLoader resourceLoader = new MultiLoader(classLoader); ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader); - BackendModuleBackendSupplier backendSupplier = new BackendModuleBackendSupplier(resourceLoader, classFinder, runtimeOptions); - eventBus = new TimeServiceEventBus(TimeService.SYSTEM); - runnerSupplier = new ThreadLocalRunnerSupplier(runtimeOptions, eventBus, backendSupplier); + ObjectFactoryServiceLoader objectFactoryServiceLoader = new ObjectFactoryServiceLoader(runtimeOptions); + ObjectFactorySupplier objectFactory = new SingletonObjectFactorySupplier(objectFactoryServiceLoader); + BackendServiceLoader backendSupplier = new BackendServiceLoader(resourceLoader, objectFactory); + eventBus = new TimeServiceEventBus(Clock.systemUTC()); + TypeRegistryConfigurerSupplier typeRegistryConfigurerSupplier = new ScanningTypeRegistryConfigurerSupplier(classFinder, runtimeOptions); + runnerSupplier = new ThreadLocalRunnerSupplier(runtimeOptions, eventBus, backendSupplier, objectFactory, typeRegistryConfigurerSupplier); } @@ -92,6 +99,6 @@ public void receive(TestCaseStarted event) { fail(); } }); - eventBus.send(new TestCaseStarted(0L, 0L, null)); + eventBus.send(new TestCaseStarted(EPOCH, mock(TestCase.class))); } } \ No newline at end of file diff --git a/core/src/test/java/cucumber/runtime/snippets/ArgumentPatternTest.java b/core/src/test/java/io/cucumber/core/snippets/ArgumentPatternTest.java similarity index 95% rename from core/src/test/java/cucumber/runtime/snippets/ArgumentPatternTest.java rename to core/src/test/java/io/cucumber/core/snippets/ArgumentPatternTest.java index d85ba1d333..a6ed184bde 100644 --- a/core/src/test/java/cucumber/runtime/snippets/ArgumentPatternTest.java +++ b/core/src/test/java/io/cucumber/core/snippets/ArgumentPatternTest.java @@ -1,4 +1,4 @@ -package cucumber.runtime.snippets; +package io.cucumber.core.snippets; import org.junit.Test; diff --git a/core/src/test/java/cucumber/runtime/snippets/FunctionNameGeneratorTest.java b/core/src/test/java/io/cucumber/core/snippets/FunctionNameGeneratorTest.java similarity index 76% rename from core/src/test/java/cucumber/runtime/snippets/FunctionNameGeneratorTest.java rename to core/src/test/java/io/cucumber/core/snippets/FunctionNameGeneratorTest.java index 8d08028732..68860ea472 100644 --- a/core/src/test/java/cucumber/runtime/snippets/FunctionNameGeneratorTest.java +++ b/core/src/test/java/io/cucumber/core/snippets/FunctionNameGeneratorTest.java @@ -1,22 +1,29 @@ -package cucumber.runtime.snippets; +package io.cucumber.core.snippets; import org.junit.Test; +import org.junit.jupiter.api.function.Executable; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; public class FunctionNameGeneratorTest { - private FunctionNameGenerator underscore = new FunctionNameGenerator(new UnderscoreConcatenator()); - private FunctionNameGenerator camelCase = new FunctionNameGenerator(new CamelCaseConcatenator()); + private FunctionNameGenerator underscore = new FunctionNameGenerator(SnippetType.UNDERSCORE.joiner()); + private FunctionNameGenerator camelCase = new FunctionNameGenerator(SnippetType.CAMELCASE.joiner()); private void assertFunctionNames(String expectedUnderscore, String expectedCamelCase, String sentence) { assertEquals(expectedUnderscore, underscore.generateFunctionName(sentence)); assertEquals(expectedCamelCase, camelCase.generateFunctionName(sentence)); } - @Test(expected = IllegalArgumentException.class) + @Test public void testSanitizeEmptyFunctionName() { - underscore.generateFunctionName(""); + final Executable testMethod = () -> underscore.generateFunctionName(""); + final IllegalArgumentException expectedThrown = assertThrows(IllegalArgumentException.class, testMethod); + assertThat(expectedThrown.getMessage(), is(equalTo("Cannot create function name from empty sentence"))); } @Test diff --git a/core/src/test/java/io/cucumber/core/snippets/TestSnippet.java b/core/src/test/java/io/cucumber/core/snippets/TestSnippet.java new file mode 100644 index 0000000000..3596ddf0ef --- /dev/null +++ b/core/src/test/java/io/cucumber/core/snippets/TestSnippet.java @@ -0,0 +1,27 @@ +package io.cucumber.core.snippets; + +import java.lang.reflect.Type; +import java.text.MessageFormat; +import java.util.Map; + +public class TestSnippet implements Snippet { + @Override + public MessageFormat template() { + return new MessageFormat(""); + } + + @Override + public String tableHint() { + return ""; + } + + @Override + public String arguments(Map arguments) { + return ""; + } + + @Override + public String escapePattern(String pattern) { + return ""; + } +} diff --git a/core/src/test/java/io/cucumber/stepexpression/StepExpressionFactoryTest.java b/core/src/test/java/io/cucumber/core/stepexpression/StepExpressionFactoryTest.java similarity index 98% rename from core/src/test/java/io/cucumber/stepexpression/StepExpressionFactoryTest.java rename to core/src/test/java/io/cucumber/core/stepexpression/StepExpressionFactoryTest.java index 2bb3491b2b..227e39e73a 100644 --- a/core/src/test/java/io/cucumber/stepexpression/StepExpressionFactoryTest.java +++ b/core/src/test/java/io/cucumber/core/stepexpression/StepExpressionFactoryTest.java @@ -1,4 +1,4 @@ -package io.cucumber.stepexpression; +package io.cucumber.core.stepexpression; import io.cucumber.datatable.DataTable; import io.cucumber.datatable.DataTableType; @@ -14,7 +14,6 @@ import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; public class StepExpressionFactoryTest { diff --git a/core/src/test/java/io/cucumber/stepexpression/TableParser.java b/core/src/test/java/io/cucumber/core/stepexpression/TableParser.java similarity index 90% rename from core/src/test/java/io/cucumber/stepexpression/TableParser.java rename to core/src/test/java/io/cucumber/core/stepexpression/TableParser.java index 028232ad47..2f9fbf911c 100644 --- a/core/src/test/java/io/cucumber/stepexpression/TableParser.java +++ b/core/src/test/java/io/cucumber/core/stepexpression/TableParser.java @@ -1,4 +1,4 @@ -package io.cucumber.stepexpression; +package io.cucumber.core.stepexpression; import io.cucumber.datatable.DataTable; import io.cucumber.datatable.DataTable.TableConverter; @@ -11,7 +11,7 @@ import java.util.List; -import static io.cucumber.stepexpression.PickleTableConverter.toTable; +import static io.cucumber.core.stepexpression.PickleTableConverter.toTable; public class TableParser { diff --git a/core/src/test/java/io/cucumber/stepexpression/TypeRegistryTest.java b/core/src/test/java/io/cucumber/core/stepexpression/TypeRegistryTest.java similarity index 96% rename from core/src/test/java/io/cucumber/stepexpression/TypeRegistryTest.java rename to core/src/test/java/io/cucumber/core/stepexpression/TypeRegistryTest.java index 18ab53ebf1..6f081b95b0 100644 --- a/core/src/test/java/io/cucumber/stepexpression/TypeRegistryTest.java +++ b/core/src/test/java/io/cucumber/core/stepexpression/TypeRegistryTest.java @@ -1,5 +1,6 @@ -package io.cucumber.stepexpression; +package io.cucumber.core.stepexpression; +import io.cucumber.core.stepexpression.TypeRegistry; import io.cucumber.cucumberexpressions.ParameterByTypeTransformer; import io.cucumber.cucumberexpressions.ParameterType; import io.cucumber.cucumberexpressions.Transformer; diff --git a/core/src/test/resources/META-INF/services/cucumber.runtime.io.ResourceIteratorFactory b/core/src/test/resources/META-INF/services/cucumber.runtime.io.ResourceIteratorFactory deleted file mode 100644 index 5c9eff2aaf..0000000000 --- a/core/src/test/resources/META-INF/services/cucumber.runtime.io.ResourceIteratorFactory +++ /dev/null @@ -1 +0,0 @@ -cucumber.runtime.io.TestResourceIteratorFactory diff --git a/core/src/test/resources/META-INF/services/io.cucumber.core.backend.BackendProviderService b/core/src/test/resources/META-INF/services/io.cucumber.core.backend.BackendProviderService new file mode 100644 index 0000000000..2676ff3305 --- /dev/null +++ b/core/src/test/resources/META-INF/services/io.cucumber.core.backend.BackendProviderService @@ -0,0 +1 @@ +io.cucumber.core.backend.StubBackendProviderService \ No newline at end of file diff --git a/core/src/test/resources/META-INF/services/io.cucumber.core.io.ResourceIteratorFactory b/core/src/test/resources/META-INF/services/io.cucumber.core.io.ResourceIteratorFactory new file mode 100644 index 0000000000..870ec1a23c --- /dev/null +++ b/core/src/test/resources/META-INF/services/io.cucumber.core.io.ResourceIteratorFactory @@ -0,0 +1 @@ +io.cucumber.core.io.TestResourceIteratorFactory diff --git a/core/src/test/resources/cucumber/runtime/bar.properties b/core/src/test/resources/io/cucumber/core/bar.properties similarity index 100% rename from core/src/test/resources/cucumber/runtime/bar.properties rename to core/src/test/resources/io/cucumber/core/bar.properties diff --git a/core/src/test/resources/cucumber/util/UTF_8_BOM_Encoded.feature b/core/src/test/resources/io/cucumber/core/feature/UTF_8_BOM_Encoded.feature similarity index 100% rename from core/src/test/resources/cucumber/util/UTF_8_BOM_Encoded.feature rename to core/src/test/resources/io/cucumber/core/feature/UTF_8_BOM_Encoded.feature diff --git a/core/src/test/resources/cucumber/util/UTF_8_Encoded.feature b/core/src/test/resources/io/cucumber/core/feature/UTF_8_Encoded.feature similarity index 100% rename from core/src/test/resources/cucumber/util/UTF_8_Encoded.feature rename to core/src/test/resources/io/cucumber/core/feature/UTF_8_Encoded.feature diff --git a/core/src/test/resources/cucumber/runtime/foo.properties b/core/src/test/resources/io/cucumber/core/foo.properties similarity index 100% rename from core/src/test/resources/cucumber/runtime/foo.properties rename to core/src/test/resources/io/cucumber/core/foo.properties diff --git a/core/src/test/resources/cucumber/runtime/has spaces.properties b/core/src/test/resources/io/cucumber/core/has spaces.properties similarity index 100% rename from core/src/test/resources/cucumber/runtime/has spaces.properties rename to core/src/test/resources/io/cucumber/core/has spaces.properties diff --git a/core/src/test/resources/cucumber/runtime/runtime-options-empty-rerun.txt b/core/src/test/resources/io/cucumber/core/options/runtime-options-empty-rerun.txt similarity index 100% rename from core/src/test/resources/cucumber/runtime/runtime-options-empty-rerun.txt rename to core/src/test/resources/io/cucumber/core/options/runtime-options-empty-rerun.txt diff --git a/core/src/test/resources/cucumber/runtime/runtime-options-rerun.txt b/core/src/test/resources/io/cucumber/core/options/runtime-options-rerun.txt similarity index 100% rename from core/src/test/resources/cucumber/runtime/runtime-options-rerun.txt rename to core/src/test/resources/io/cucumber/core/options/runtime-options-rerun.txt diff --git a/core/src/test/resources/cucumber/runtime/formatter/FormatterInParallel.feature b/core/src/test/resources/io/cucumber/core/plugin/FormatterInParallel.feature similarity index 100% rename from core/src/test/resources/cucumber/runtime/formatter/FormatterInParallel.feature rename to core/src/test/resources/io/cucumber/core/plugin/FormatterInParallel.feature diff --git a/core/src/test/resources/cucumber/runtime/formatter/HTMLFormatterTest.feature b/core/src/test/resources/io/cucumber/core/plugin/HTMLFormatterTest.feature similarity index 100% rename from core/src/test/resources/cucumber/runtime/formatter/HTMLFormatterTest.feature rename to core/src/test/resources/io/cucumber/core/plugin/HTMLFormatterTest.feature diff --git a/core/src/test/resources/cucumber/runtime/formatter/JSONPrettyFormatterTest.feature b/core/src/test/resources/io/cucumber/core/plugin/JSONPrettyFormatterTest.feature similarity index 100% rename from core/src/test/resources/cucumber/runtime/formatter/JSONPrettyFormatterTest.feature rename to core/src/test/resources/io/cucumber/core/plugin/JSONPrettyFormatterTest.feature diff --git a/core/src/test/resources/cucumber/runtime/formatter/JSONPrettyFormatterTest.json b/core/src/test/resources/io/cucumber/core/plugin/JSONPrettyFormatterTest.json similarity index 98% rename from core/src/test/resources/cucumber/runtime/formatter/JSONPrettyFormatterTest.json rename to core/src/test/resources/io/cucumber/core/plugin/JSONPrettyFormatterTest.json index e7ccb766df..a272c77fe7 100644 --- a/core/src/test/resources/cucumber/runtime/formatter/JSONPrettyFormatterTest.json +++ b/core/src/test/resources/io/cucumber/core/plugin/JSONPrettyFormatterTest.json @@ -348,7 +348,7 @@ "type": "scenario" } ], - "uri": "classpath:cucumber/runtime/formatter/JSONPrettyFormatterTest.feature", + "uri": "classpath:io/cucumber/core/plugin/JSONPrettyFormatterTest.feature", "tags": [] } ] \ No newline at end of file diff --git a/core/src/test/resources/cucumber/runtime/formatter/JUnitFormatterTest_1.feature b/core/src/test/resources/io/cucumber/core/plugin/JUnitFormatterTest_1.feature similarity index 100% rename from core/src/test/resources/cucumber/runtime/formatter/JUnitFormatterTest_1.feature rename to core/src/test/resources/io/cucumber/core/plugin/JUnitFormatterTest_1.feature diff --git a/core/src/test/resources/cucumber/runtime/formatter/JUnitFormatterTest_1.report.xml b/core/src/test/resources/io/cucumber/core/plugin/JUnitFormatterTest_1.report.xml similarity index 88% rename from core/src/test/resources/cucumber/runtime/formatter/JUnitFormatterTest_1.report.xml rename to core/src/test/resources/io/cucumber/core/plugin/JUnitFormatterTest_1.report.xml index 1f37c70ccd..4874af0802 100644 --- a/core/src/test/resources/cucumber/runtime/formatter/JUnitFormatterTest_1.report.xml +++ b/core/src/test/resources/io/cucumber/core/plugin/JUnitFormatterTest_1.report.xml @@ -1,5 +1,5 @@ - + - + - + - + io.cucumber cucumber-examples - 4.7.2-SNAPSHOT + 5.0.0-SNAPSHOT java-calculator-testng @@ -23,18 +23,4 @@ test - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - - - - diff --git a/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/DateSteps.java b/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/DateStepdefs.java similarity index 90% rename from examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/DateSteps.java rename to examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/DateStepdefs.java index c2c6459a6e..fd7c300521 100644 --- a/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/DateSteps.java +++ b/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/DateStepdefs.java @@ -1,5 +1,6 @@ package io.cucumber.examples.testng; +import io.cucumber.examples.testng.DateCalculator; import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; @@ -8,7 +9,7 @@ import static org.testng.Assert.assertEquals; -public class DateSteps { +public class DateStepdefs { private String result; private DateCalculator calculator; diff --git a/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/ParameterTypes.java b/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/ParameterTypes.java index 3c30abd86f..ab3931ba20 100644 --- a/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/ParameterTypes.java +++ b/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/ParameterTypes.java @@ -1,10 +1,8 @@ package io.cucumber.examples.testng; -import io.cucumber.core.api.TypeRegistry; import io.cucumber.core.api.TypeRegistryConfigurer; -import io.cucumber.examples.testng.ShoppingSteps.Price; +import io.cucumber.core.api.TypeRegistry; import io.cucumber.datatable.DataTableType; -import io.cucumber.examples.testng.ShoppingSteps.Grocery; import io.cucumber.cucumberexpressions.ParameterType; import java.text.SimpleDateFormat; @@ -12,7 +10,7 @@ import java.util.Locale; import java.util.Map; -import static io.cucumber.examples.testng.RpnCalculatorSteps.Entry; +import static io.cucumber.examples.testng.RpnCalculatorStepdefs.Entry; import static java.text.DateFormat.MEDIUM; import static java.text.DateFormat.getDateInstance; import static java.util.Locale.ENGLISH; @@ -46,10 +44,10 @@ public void configureTypeRegistry(TypeRegistry typeRegistry) { )); typeRegistry.defineDataTableType(new DataTableType( - Grocery.class, - (Map row) -> new Grocery( + ShoppingStepdefs.Grocery.class, + (Map row) -> new ShoppingStepdefs.Grocery( row.get("name"), - Price.fromString(row.get("price")) + ShoppingStepdefs.Price.fromString(row.get("price")) ) )); diff --git a/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/RpnCalculatorSteps.java b/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/RpnCalculatorStepdefs.java similarity index 95% rename from examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/RpnCalculatorSteps.java rename to examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/RpnCalculatorStepdefs.java index bc848d8735..d839ea87e6 100644 --- a/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/RpnCalculatorSteps.java +++ b/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/RpnCalculatorStepdefs.java @@ -1,6 +1,7 @@ package io.cucumber.examples.testng; import io.cucumber.core.api.Scenario; +import io.cucumber.examples.testng.RpnCalculator; import io.cucumber.java.After; import io.cucumber.java.Before; import io.cucumber.java.en.Given; @@ -11,7 +12,7 @@ import static org.testng.Assert.assertEquals; -public class RpnCalculatorSteps { +public class RpnCalculatorStepdefs { private RpnCalculator calc; @Given("a calculator I just turned on") diff --git a/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/RunCucumberTest.java b/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/RunCucumberTest.java index 669dc5c7c9..067a8f84c5 100644 --- a/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/RunCucumberTest.java +++ b/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/RunCucumberTest.java @@ -4,7 +4,7 @@ import io.cucumber.testng.CucumberOptions; import org.testng.annotations.DataProvider; -@CucumberOptions(plugin = {"json:target/cucumber-report.json"}) +@CucumberOptions(plugin = {"summary","json:target/cucumber-report.json"}) public class RunCucumberTest extends AbstractTestNGCucumberTests { @DataProvider(parallel = true) diff --git a/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/ShoppingSteps.java b/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/ShoppingStepdefs.java similarity index 93% rename from examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/ShoppingSteps.java rename to examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/ShoppingStepdefs.java index 68888a02db..3eb32632ca 100644 --- a/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/ShoppingSteps.java +++ b/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/ShoppingStepdefs.java @@ -1,5 +1,6 @@ package io.cucumber.examples.testng; +import io.cucumber.examples.testng.RpnCalculator; import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; @@ -8,7 +9,7 @@ import static org.testng.Assert.assertEquals; -public class ShoppingSteps { +public class ShoppingStepdefs { private RpnCalculator calc = new RpnCalculator(); @Given("the following groceries:") diff --git a/examples/java-calculator-testng/src/test/resources/io/cucumber/examples/testng/basic_arithmetic.feature b/examples/java-calculator-testng/src/test/resources/io/cucumber/examples/java8/basic_arithmetic.feature similarity index 100% rename from examples/java-calculator-testng/src/test/resources/io/cucumber/examples/testng/basic_arithmetic.feature rename to examples/java-calculator-testng/src/test/resources/io/cucumber/examples/java8/basic_arithmetic.feature diff --git a/examples/java-calculator-testng/src/test/resources/io/cucumber/examples/testng/date_calculator.feature b/examples/java-calculator-testng/src/test/resources/io/cucumber/examples/java8/date_calculator.feature similarity index 100% rename from examples/java-calculator-testng/src/test/resources/io/cucumber/examples/testng/date_calculator.feature rename to examples/java-calculator-testng/src/test/resources/io/cucumber/examples/java8/date_calculator.feature diff --git a/examples/java-calculator-testng/src/test/resources/io/cucumber/examples/testng/shopping.feature b/examples/java-calculator-testng/src/test/resources/io/cucumber/examples/java8/shopping.feature similarity index 100% rename from examples/java-calculator-testng/src/test/resources/io/cucumber/examples/testng/shopping.feature rename to examples/java-calculator-testng/src/test/resources/io/cucumber/examples/java8/shopping.feature diff --git a/examples/java-calculator/pom.xml b/examples/java-calculator/pom.xml index fdb1dc7e5c..253c79523e 100644 --- a/examples/java-calculator/pom.xml +++ b/examples/java-calculator/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-examples - 4.7.2-SNAPSHOT + 5.0.0-SNAPSHOT java-calculator @@ -38,14 +38,6 @@ 4 - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - - diff --git a/examples/java-calculator/src/main/java/io/cucumber/examples/java/DateCalculator.java b/examples/java-calculator/src/main/java/io/cucumber/examples/java/DateCalculator.java index c5719987ec..ecd2f98e6b 100644 --- a/examples/java-calculator/src/main/java/io/cucumber/examples/java/DateCalculator.java +++ b/examples/java-calculator/src/main/java/io/cucumber/examples/java/DateCalculator.java @@ -1,15 +1,15 @@ package io.cucumber.examples.java; -import java.util.Date; +import java.time.LocalDate; public class DateCalculator { - private Date now; + private LocalDate now; - public DateCalculator(Date now) { + public DateCalculator(LocalDate now) { this.now = now; } - public String isDateInThePast(Date date) { - return (date.before(now)) ? "yes" : "no"; + public String isDateInThePast(LocalDate date) { + return (date.isBefore(now)) ? "yes" : "no"; } } diff --git a/examples/java-calculator/src/test/java/io/cucumber/examples/java/DateStepdefs.java b/examples/java-calculator/src/test/java/io/cucumber/examples/java/DateStepdefs.java new file mode 100644 index 0000000000..8c6a2473ad --- /dev/null +++ b/examples/java-calculator/src/test/java/io/cucumber/examples/java/DateStepdefs.java @@ -0,0 +1,35 @@ +package io.cucumber.examples.java; + +import io.cucumber.java.ParameterType; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; + +import java.time.LocalDate; + +import static org.junit.Assert.assertEquals; + +public class DateStepdefs { + private String result; + private DateCalculator calculator; + + @ParameterType("([0-9]{4})-([0-9]{2})-([0-9]{2})") + public LocalDate iso8601Date(String year, String month, String day){ + return LocalDate.of(Integer.parseInt(year), Integer.parseInt(month), Integer.parseInt(day)); + } + + @Given("today is {iso8601Date}") + public void today_is(LocalDate date) { + calculator = new DateCalculator(date); + } + + @When("I ask if {iso8601Date} is in the past") + public void I_ask_if_date_is_in_the_past(LocalDate date) { + result = calculator.isDateInThePast(date); + } + + @Then("^the result should be (yes|no)$") + public void the_result_should_be(String expectedResult) { + assertEquals(expectedResult, result); + } +} diff --git a/examples/java-calculator/src/test/java/io/cucumber/examples/java/DateSteps.java b/examples/java-calculator/src/test/java/io/cucumber/examples/java/DateSteps.java deleted file mode 100644 index 39c172a9de..0000000000 --- a/examples/java-calculator/src/test/java/io/cucumber/examples/java/DateSteps.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.cucumber.examples.java; - -import io.cucumber.examples.java.DateCalculator; -import io.cucumber.java.en.Given; -import io.cucumber.java.en.Then; -import io.cucumber.java.en.When; - -import java.util.Date; - -import static org.junit.Assert.assertEquals; - -public class DateSteps { - private String result; - private DateCalculator calculator; - - @Given("^today is ([0-9]{4}-[0-9]{2}-[0-9]{2})$") - public void today_is(Date date) { - calculator = new DateCalculator(date); - } - - @When("^I ask if ([0-9]{4}-[0-9]{2}-[0-9]{2}) is in the past$") - public void I_ask_if_date_is_in_the_past(Date date) { - result = calculator.isDateInThePast(date); - } - - @Then("^the result should be (yes|no)$") - public void the_result_should_be(String expectedResult) { - assertEquals(expectedResult, result); - } -} diff --git a/examples/java-calculator/src/test/java/io/cucumber/examples/java/Example.java b/examples/java-calculator/src/test/java/io/cucumber/examples/java/Example.java deleted file mode 100644 index 0ace137188..0000000000 --- a/examples/java-calculator/src/test/java/io/cucumber/examples/java/Example.java +++ /dev/null @@ -1,4 +0,0 @@ -package io.cucumber.examples.java; - -public interface Example { -} diff --git a/examples/java-calculator/src/test/java/io/cucumber/examples/java/RpnCalculatorSteps.java b/examples/java-calculator/src/test/java/io/cucumber/examples/java/RpnCalculatorStepdefs.java similarity index 98% rename from examples/java-calculator/src/test/java/io/cucumber/examples/java/RpnCalculatorSteps.java rename to examples/java-calculator/src/test/java/io/cucumber/examples/java/RpnCalculatorStepdefs.java index 36e77041b1..47077aa2ba 100644 --- a/examples/java-calculator/src/test/java/io/cucumber/examples/java/RpnCalculatorSteps.java +++ b/examples/java-calculator/src/test/java/io/cucumber/examples/java/RpnCalculatorStepdefs.java @@ -11,7 +11,7 @@ import static org.junit.Assert.assertEquals; -public class RpnCalculatorSteps { +public class RpnCalculatorStepdefs { private RpnCalculator calc; @Given("a calculator I just turned on") diff --git a/examples/java-calculator/src/test/java/io/cucumber/examples/java/ShoppingSteps.java b/examples/java-calculator/src/test/java/io/cucumber/examples/java/ShoppingStepdefs.java similarity index 94% rename from examples/java-calculator/src/test/java/io/cucumber/examples/java/ShoppingSteps.java rename to examples/java-calculator/src/test/java/io/cucumber/examples/java/ShoppingStepdefs.java index dfdce9c48c..dec2fd8199 100644 --- a/examples/java-calculator/src/test/java/io/cucumber/examples/java/ShoppingSteps.java +++ b/examples/java-calculator/src/test/java/io/cucumber/examples/java/ShoppingStepdefs.java @@ -1,6 +1,5 @@ package io.cucumber.examples.java; -import io.cucumber.examples.java.RpnCalculator; import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; @@ -9,7 +8,7 @@ import static org.junit.Assert.assertEquals; -public class ShoppingSteps { +public class ShoppingStepdefs { private RpnCalculator calc = new RpnCalculator(); @Given("the following groceries:") diff --git a/examples/java-wicket/java-wicket-main/pom.xml b/examples/java-wicket/java-wicket-main/pom.xml index bd0ff98bd3..733198c24e 100644 --- a/examples/java-wicket/java-wicket-main/pom.xml +++ b/examples/java-wicket/java-wicket-main/pom.xml @@ -3,7 +3,7 @@ io.cucumber java-wicket - 4.7.2-SNAPSHOT + 5.0.0-SNAPSHOT java-wicket-main Examples: Wicket application diff --git a/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/wicket/Application.java b/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/java/wicket/Application.java similarity index 65% rename from examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/wicket/Application.java rename to examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/java/wicket/Application.java index 79c70125ca..8bffccd318 100644 --- a/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/wicket/Application.java +++ b/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/java/wicket/Application.java @@ -1,11 +1,11 @@ -package io.cucumber.examples.wicket; +package io.cucumber.examples.java.wicket; -import io.cucumber.examples.wicket.model.dao.CarDAO; -import io.cucumber.examples.wicket.model.dao.InMemoryCarDAO; -import io.cucumber.examples.wicket.model.entity.Car; -import io.cucumber.examples.wicket.view.Available; -import io.cucumber.examples.wicket.view.Create; -import io.cucumber.examples.wicket.view.Rent; +import io.cucumber.examples.java.wicket.model.dao.CarDAO; +import io.cucumber.examples.java.wicket.model.dao.InMemoryCarDAO; +import io.cucumber.examples.java.wicket.model.entity.Car; +import io.cucumber.examples.java.wicket.view.Available; +import io.cucumber.examples.java.wicket.view.Create; +import io.cucumber.examples.java.wicket.view.Rent; import org.apache.wicket.protocol.http.WebApplication; public class Application extends WebApplication { diff --git a/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/wicket/model/dao/CarDAO.java b/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/java/wicket/model/dao/CarDAO.java similarity index 53% rename from examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/wicket/model/dao/CarDAO.java rename to examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/java/wicket/model/dao/CarDAO.java index 6dadf81a47..54429a0bb5 100644 --- a/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/wicket/model/dao/CarDAO.java +++ b/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/java/wicket/model/dao/CarDAO.java @@ -1,6 +1,6 @@ -package io.cucumber.examples.wicket.model.dao; +package io.cucumber.examples.java.wicket.model.dao; -import io.cucumber.examples.wicket.model.entity.Car; +import io.cucumber.examples.java.wicket.model.entity.Car; public interface CarDAO { public void add(Car car); diff --git a/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/wicket/model/dao/InMemoryCarDAO.java b/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/java/wicket/model/dao/InMemoryCarDAO.java similarity index 88% rename from examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/wicket/model/dao/InMemoryCarDAO.java rename to examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/java/wicket/model/dao/InMemoryCarDAO.java index de9f51e24a..ab0d2e4cbf 100644 --- a/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/wicket/model/dao/InMemoryCarDAO.java +++ b/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/java/wicket/model/dao/InMemoryCarDAO.java @@ -1,6 +1,6 @@ -package io.cucumber.examples.wicket.model.dao; +package io.cucumber.examples.java.wicket.model.dao; -import io.cucumber.examples.wicket.model.entity.Car; +import io.cucumber.examples.java.wicket.model.entity.Car; import java.util.LinkedList; import java.util.List; diff --git a/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/wicket/model/entity/Car.java b/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/java/wicket/model/entity/Car.java similarity index 75% rename from examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/wicket/model/entity/Car.java rename to examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/java/wicket/model/entity/Car.java index 3d0bf0b5fe..2940d4b99a 100644 --- a/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/wicket/model/entity/Car.java +++ b/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/java/wicket/model/entity/Car.java @@ -1,4 +1,4 @@ -package io.cucumber.examples.wicket.model.entity; +package io.cucumber.examples.java.wicket.model.entity; public class Car { private boolean rented; diff --git a/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/wicket/view/Available.java b/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/java/wicket/view/Available.java similarity index 81% rename from examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/wicket/view/Available.java rename to examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/java/wicket/view/Available.java index 451f116de0..1c5c81e217 100644 --- a/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/wicket/view/Available.java +++ b/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/java/wicket/view/Available.java @@ -1,6 +1,6 @@ -package io.cucumber.examples.wicket.view; +package io.cucumber.examples.java.wicket.view; -import io.cucumber.examples.wicket.Application; +import io.cucumber.examples.java.wicket.Application; import org.apache.wicket.markup.html.WebPage; import org.apache.wicket.markup.html.basic.Label; diff --git a/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/wicket/view/Create.java b/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/java/wicket/view/Create.java similarity index 93% rename from examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/wicket/view/Create.java rename to examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/java/wicket/view/Create.java index 9c30199759..51a431e0e1 100644 --- a/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/wicket/view/Create.java +++ b/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/java/wicket/view/Create.java @@ -1,6 +1,6 @@ -package io.cucumber.examples.wicket.view; +package io.cucumber.examples.java.wicket.view; -import io.cucumber.examples.wicket.Application; +import io.cucumber.examples.java.wicket.Application; import org.apache.wicket.markup.html.WebPage; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.form.FormComponent; diff --git a/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/wicket/view/Rent.java b/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/java/wicket/view/Rent.java similarity index 88% rename from examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/wicket/view/Rent.java rename to examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/java/wicket/view/Rent.java index 2d25bcb16b..a3e2c12270 100644 --- a/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/wicket/view/Rent.java +++ b/examples/java-wicket/java-wicket-main/src/main/java/io/cucumber/examples/java/wicket/view/Rent.java @@ -1,6 +1,6 @@ -package io.cucumber.examples.wicket.view; +package io.cucumber.examples.java.wicket.view; -import io.cucumber.examples.wicket.Application; +import io.cucumber.examples.java.wicket.Application; import org.apache.wicket.markup.html.WebPage; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.model.CompoundPropertyModel; diff --git a/examples/java-wicket/java-wicket-main/src/main/resources/io/cucumber/examples/wicket/view/Available.html b/examples/java-wicket/java-wicket-main/src/main/resources/io/cucumber/examples/java/wicket/view/Available.html similarity index 100% rename from examples/java-wicket/java-wicket-main/src/main/resources/io/cucumber/examples/wicket/view/Available.html rename to examples/java-wicket/java-wicket-main/src/main/resources/io/cucumber/examples/java/wicket/view/Available.html diff --git a/examples/java-wicket/java-wicket-main/src/main/resources/io/cucumber/examples/wicket/view/Create.html b/examples/java-wicket/java-wicket-main/src/main/resources/io/cucumber/examples/java/wicket/view/Create.html similarity index 100% rename from examples/java-wicket/java-wicket-main/src/main/resources/io/cucumber/examples/wicket/view/Create.html rename to examples/java-wicket/java-wicket-main/src/main/resources/io/cucumber/examples/java/wicket/view/Create.html diff --git a/examples/java-wicket/java-wicket-main/src/main/resources/io/cucumber/examples/wicket/view/Rent.html b/examples/java-wicket/java-wicket-main/src/main/resources/io/cucumber/examples/java/wicket/view/Rent.html similarity index 100% rename from examples/java-wicket/java-wicket-main/src/main/resources/io/cucumber/examples/wicket/view/Rent.html rename to examples/java-wicket/java-wicket-main/src/main/resources/io/cucumber/examples/java/wicket/view/Rent.html diff --git a/examples/java-wicket/java-wicket-main/src/main/webapp/WEB-INF/web.xml b/examples/java-wicket/java-wicket-main/src/main/webapp/WEB-INF/web.xml index a151341493..81194cac23 100644 --- a/examples/java-wicket/java-wicket-main/src/main/webapp/WEB-INF/web.xml +++ b/examples/java-wicket/java-wicket-main/src/main/webapp/WEB-INF/web.xml @@ -7,7 +7,7 @@ org.apache.wicket.protocol.http.WicketFilter applicationClassName - io.cucumber.examples.wicket.Application + io.cucumber.examples.java.wicket.Application diff --git a/examples/java-wicket/java-wicket-main/src/test/java/io/cucumber/examples/wicket/RentCarTest.java b/examples/java-wicket/java-wicket-main/src/test/java/io/cucumber/examples/java/wicket/RentCarTest.java similarity index 81% rename from examples/java-wicket/java-wicket-main/src/test/java/io/cucumber/examples/wicket/RentCarTest.java rename to examples/java-wicket/java-wicket-main/src/test/java/io/cucumber/examples/java/wicket/RentCarTest.java index 612e9d9a9e..68bae27248 100644 --- a/examples/java-wicket/java-wicket-main/src/test/java/io/cucumber/examples/wicket/RentCarTest.java +++ b/examples/java-wicket/java-wicket-main/src/test/java/io/cucumber/examples/java/wicket/RentCarTest.java @@ -1,8 +1,8 @@ -package io.cucumber.examples.wicket; +package io.cucumber.examples.java.wicket; -import io.cucumber.examples.wicket.view.Available; -import io.cucumber.examples.wicket.view.Create; -import io.cucumber.examples.wicket.view.Rent; +import io.cucumber.examples.java.wicket.view.Available; +import io.cucumber.examples.java.wicket.view.Create; +import io.cucumber.examples.java.wicket.view.Rent; import org.apache.wicket.util.tester.WicketTester; import org.junit.Test; diff --git a/examples/java-wicket/java-wicket-test/pom.xml b/examples/java-wicket/java-wicket-test/pom.xml index 83b8bc626c..3f1ef4bc61 100644 --- a/examples/java-wicket/java-wicket-test/pom.xml +++ b/examples/java-wicket/java-wicket-test/pom.xml @@ -3,7 +3,7 @@ io.cucumber java-wicket - 4.7.2-SNAPSHOT + 5.0.0-SNAPSHOT java-wicket-test Examples: Wicket application tested with Selenium diff --git a/examples/java-wicket/java-wicket-test/src/test/java/io/cucumber/examples/wicket/RunCucumberIT.java b/examples/java-wicket/java-wicket-test/src/test/java/io/cucumber/examples/java/wicket/RunCucumberIT.java similarity index 75% rename from examples/java-wicket/java-wicket-test/src/test/java/io/cucumber/examples/wicket/RunCucumberIT.java rename to examples/java-wicket/java-wicket-test/src/test/java/io/cucumber/examples/java/wicket/RunCucumberIT.java index 7c4092c380..77b043d9e6 100644 --- a/examples/java-wicket/java-wicket-test/src/test/java/io/cucumber/examples/wicket/RunCucumberIT.java +++ b/examples/java-wicket/java-wicket-test/src/test/java/io/cucumber/examples/java/wicket/RunCucumberIT.java @@ -1,4 +1,4 @@ -package io.cucumber.examples.wicket; +package io.cucumber.examples.java.wicket; import io.cucumber.junit.Cucumber; import org.junit.runner.RunWith; diff --git a/examples/java-wicket/java-wicket-test/src/test/java/io/cucumber/examples/wicket/steps/RentACarSupport.java b/examples/java-wicket/java-wicket-test/src/test/java/io/cucumber/examples/java/wicket/steps/RentACarSupport.java similarity index 96% rename from examples/java-wicket/java-wicket-test/src/test/java/io/cucumber/examples/wicket/steps/RentACarSupport.java rename to examples/java-wicket/java-wicket-test/src/test/java/io/cucumber/examples/java/wicket/steps/RentACarSupport.java index 851fdf44c6..180a031e73 100644 --- a/examples/java-wicket/java-wicket-test/src/test/java/io/cucumber/examples/wicket/steps/RentACarSupport.java +++ b/examples/java-wicket/java-wicket-test/src/test/java/io/cucumber/examples/java/wicket/steps/RentACarSupport.java @@ -1,4 +1,4 @@ -package io.cucumber.examples.wicket.steps; +package io.cucumber.examples.java.wicket.steps; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; diff --git a/examples/java-wicket/java-wicket-test/src/test/java/io/cucumber/examples/wicket/steps/RentACarSteps.java b/examples/java-wicket/java-wicket-test/src/test/java/io/cucumber/examples/java/wicket/steps/RentStepdefs.java similarity index 92% rename from examples/java-wicket/java-wicket-test/src/test/java/io/cucumber/examples/wicket/steps/RentACarSteps.java rename to examples/java-wicket/java-wicket-test/src/test/java/io/cucumber/examples/java/wicket/steps/RentStepdefs.java index 361a42c31e..bb140d2b3f 100644 --- a/examples/java-wicket/java-wicket-test/src/test/java/io/cucumber/examples/wicket/steps/RentACarSteps.java +++ b/examples/java-wicket/java-wicket-test/src/test/java/io/cucumber/examples/java/wicket/steps/RentStepdefs.java @@ -1,4 +1,4 @@ -package io.cucumber.examples.wicket.steps; +package io.cucumber.examples.java.wicket.steps; import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; @@ -7,7 +7,7 @@ import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; -public class RentACarSteps { +public class RentStepdefs { private RentACarSupport rentACarSupport = new RentACarSupport(); @Given("there are {int} cars available for rental") diff --git a/examples/java-wicket/java-wicket-test/src/test/resources/io/cucumber/examples/wicket/Rent.feature b/examples/java-wicket/java-wicket-test/src/test/resources/io/cucumber/examples/java/wicket/Rent.feature similarity index 100% rename from examples/java-wicket/java-wicket-test/src/test/resources/io/cucumber/examples/wicket/Rent.feature rename to examples/java-wicket/java-wicket-test/src/test/resources/io/cucumber/examples/java/wicket/Rent.feature diff --git a/examples/java-wicket/pom.xml b/examples/java-wicket/pom.xml index b1200ac7cd..202600c6a4 100644 --- a/examples/java-wicket/pom.xml +++ b/examples/java-wicket/pom.xml @@ -3,7 +3,7 @@ io.cucumber cucumber-examples - 4.7.2-SNAPSHOT + 5.0.0-SNAPSHOT java-wicket pom diff --git a/examples/java8-calculator/pom.xml b/examples/java8-calculator/pom.xml index 309a306cde..50083f5bc4 100644 --- a/examples/java8-calculator/pom.xml +++ b/examples/java8-calculator/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-examples - 4.7.2-SNAPSHOT + 5.0.0-SNAPSHOT java8-calculator @@ -28,20 +28,4 @@ test - - - - - org.apache.maven.plugins - maven-compiler-plugin - - true - UTF-8 - 1.8 - 1.8 - -XDignore.symbol.file=true - - - - diff --git a/examples/java8-calculator/src/test/java/io/cucumber/examples/java8/RpnCalculatorSteps.java b/examples/java8-calculator/src/test/java/io/cucumber/examples/java8/RpnCalculatorStepdefs.java similarity index 95% rename from examples/java8-calculator/src/test/java/io/cucumber/examples/java8/RpnCalculatorSteps.java rename to examples/java8-calculator/src/test/java/io/cucumber/examples/java8/RpnCalculatorStepdefs.java index a7202a3a33..8811b62a3c 100644 --- a/examples/java8-calculator/src/test/java/io/cucumber/examples/java8/RpnCalculatorSteps.java +++ b/examples/java8-calculator/src/test/java/io/cucumber/examples/java8/RpnCalculatorStepdefs.java @@ -8,10 +8,10 @@ import static org.junit.Assert.assertEquals; -public class RpnCalculatorSteps implements En { +public class RpnCalculatorStepdefs implements En { private RpnCalculator calc; - public RpnCalculatorSteps() { + public RpnCalculatorStepdefs() { Given("a calculator I just turned on", () -> { calc = new RpnCalculator(); }); diff --git a/examples/java8-calculator/src/test/java/io/cucumber/examples/java8/ShoppingSteps.java b/examples/java8-calculator/src/test/java/io/cucumber/examples/java8/ShoppingStepdefs.java similarity index 94% rename from examples/java8-calculator/src/test/java/io/cucumber/examples/java8/ShoppingSteps.java rename to examples/java8-calculator/src/test/java/io/cucumber/examples/java8/ShoppingStepdefs.java index 16b88c0c2a..530edee6cf 100644 --- a/examples/java8-calculator/src/test/java/io/cucumber/examples/java8/ShoppingSteps.java +++ b/examples/java8-calculator/src/test/java/io/cucumber/examples/java8/ShoppingStepdefs.java @@ -7,12 +7,11 @@ import static org.junit.Assert.assertEquals; -public class ShoppingSteps implements En { +public class ShoppingStepdefs implements En { private RpnCalculator calc = new RpnCalculator(); - public ShoppingSteps() { - + public ShoppingStepdefs() { Given("the following groceries:", (DataTable dataTable) -> { List groceries = dataTable.asList(Grocery.class); diff --git a/examples/java8-calculator/src/test/java/io/cucumber/examples/java8/TypeRegistryConfiguration.java b/examples/java8-calculator/src/test/java/io/cucumber/examples/java8/TypeRegistryConfiguration.java index 43dc9852fd..b3c02c7000 100644 --- a/examples/java8-calculator/src/test/java/io/cucumber/examples/java8/TypeRegistryConfiguration.java +++ b/examples/java8-calculator/src/test/java/io/cucumber/examples/java8/TypeRegistryConfiguration.java @@ -1,7 +1,7 @@ package io.cucumber.examples.java8; -import io.cucumber.core.api.TypeRegistry; import io.cucumber.core.api.TypeRegistryConfigurer; +import io.cucumber.core.api.TypeRegistry; import io.cucumber.datatable.DataTableType; import java.util.Locale; @@ -19,8 +19,8 @@ public Locale locale() { @Override public void configureTypeRegistry(TypeRegistry typeRegistry) { typeRegistry.defineDataTableType(new DataTableType( - RpnCalculatorSteps.Entry.class, - (Map row) -> new RpnCalculatorSteps.Entry( + RpnCalculatorStepdefs.Entry.class, + (Map row) -> new RpnCalculatorStepdefs.Entry( Integer.valueOf(row.get("first")), Integer.valueOf(row.get("second")), row.get("operation") @@ -28,10 +28,10 @@ public void configureTypeRegistry(TypeRegistry typeRegistry) { )); typeRegistry.defineDataTableType(new DataTableType( - ShoppingSteps.Grocery.class, - (Map row) -> new ShoppingSteps.Grocery( + ShoppingStepdefs.Grocery.class, + (Map row) -> new ShoppingStepdefs.Grocery( row.get("name"), - ShoppingSteps.Price.fromString(row.get("price")) + ShoppingStepdefs.Price.fromString(row.get("price")) ) )); } diff --git a/examples/pom.xml b/examples/pom.xml index 7cdfbc4293..971b1bf0bb 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 4.7.2-SNAPSHOT + 5.0.0-SNAPSHOT cucumber-examples diff --git a/examples/spring-txn/pom.xml b/examples/spring-txn/pom.xml index ada6da064f..7469261bd4 100644 --- a/examples/spring-txn/pom.xml +++ b/examples/spring-txn/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-examples - 4.7.2-SNAPSHOT + 5.0.0-SNAPSHOT spring-txn diff --git a/examples/spring-txn/src/test/java/io/cucumber/examples/spring/txn/RunCucumberTest.java b/examples/spring-txn/src/test/java/io/cucumber/examples/spring/txn/RunCucumberTest.java index 94efb777bf..924c3f912e 100644 --- a/examples/spring-txn/src/test/java/io/cucumber/examples/spring/txn/RunCucumberTest.java +++ b/examples/spring-txn/src/test/java/io/cucumber/examples/spring/txn/RunCucumberTest.java @@ -1,8 +1,10 @@ package io.cucumber.examples.spring.txn; import io.cucumber.junit.Cucumber; +import io.cucumber.junit.CucumberOptions; import org.junit.runner.RunWith; @RunWith(Cucumber.class) +@CucumberOptions public class RunCucumberTest { } diff --git a/examples/spring-txn/src/test/java/io/cucumber/examples/spring/txn/SeeMessagesSteps.java b/examples/spring-txn/src/test/java/io/cucumber/examples/spring/txn/SeeMessagesStepdefs.java similarity index 97% rename from examples/spring-txn/src/test/java/io/cucumber/examples/spring/txn/SeeMessagesSteps.java rename to examples/spring-txn/src/test/java/io/cucumber/examples/spring/txn/SeeMessagesStepdefs.java index ff7b044b7c..4ed4358dbc 100644 --- a/examples/spring-txn/src/test/java/io/cucumber/examples/spring/txn/SeeMessagesSteps.java +++ b/examples/spring-txn/src/test/java/io/cucumber/examples/spring/txn/SeeMessagesStepdefs.java @@ -12,7 +12,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -public class SeeMessagesSteps { +public class SeeMessagesStepdefs { @Autowired private UserRepository userRepository; diff --git a/examples/spring-txn/src/test/java/io/cucumber/examples/spring/txn/SpringTransactionHooks.java b/examples/spring-txn/src/test/java/io/cucumber/examples/spring/txn/SpringTransactionHooks.java index d6cf2f0049..1eb80dcd16 100644 --- a/examples/spring-txn/src/test/java/io/cucumber/examples/spring/txn/SpringTransactionHooks.java +++ b/examples/spring-txn/src/test/java/io/cucumber/examples/spring/txn/SpringTransactionHooks.java @@ -31,7 +31,6 @@ public class SpringTransactionHooks implements BeanFactoryAware { private BeanFactory beanFactory; private String txnManagerBeanName; - private TransactionStatus transactionStatus; @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { @@ -54,6 +53,8 @@ public void setTxnManagerBeanName(String txnManagerBeanName) { this.txnManagerBeanName = txnManagerBeanName; } + private TransactionStatus transactionStatus; + @Before(value = "@txn", order = 100) public void startTransaction() { transactionStatus = obtainPlatformTransactionManager().getTransaction(new DefaultTransactionDefinition()); diff --git a/examples/spring-txn/src/test/java/io/cucumber/examples/spring/txn/TypeRegistryConfiguration.java b/examples/spring-txn/src/test/java/io/cucumber/examples/spring/txn/TypeRegistryConfiguration.java index e70c90cd0d..7b7cb66579 100644 --- a/examples/spring-txn/src/test/java/io/cucumber/examples/spring/txn/TypeRegistryConfiguration.java +++ b/examples/spring-txn/src/test/java/io/cucumber/examples/spring/txn/TypeRegistryConfiguration.java @@ -1,9 +1,10 @@ package io.cucumber.examples.spring.txn; -import io.cucumber.core.api.TypeRegistry; import io.cucumber.core.api.TypeRegistryConfigurer; -import io.cucumber.datatable.DataTableType; -import io.cucumber.datatable.TableEntryTransformer; +import io.cucumber.core.api.TypeRegistry; +import io.cucumber.datatable.TableCellByTypeTransformer; +import io.cucumber.datatable.TableEntryByTypeTransformer; +import io.cucumber.datatable.dependency.com.fasterxml.jackson.databind.ObjectMapper; import java.util.Locale; import java.util.Map; @@ -19,16 +20,24 @@ public Locale locale() { @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; - } - })); + DefaultTransformer defaultTransformer = new DefaultTransformer(); + typeRegistry.setDefaultDataTableCellTransformer(defaultTransformer); + typeRegistry.setDefaultDataTableEntryTransformer(defaultTransformer); + + } + + private class DefaultTransformer implements TableCellByTypeTransformer, TableEntryByTypeTransformer { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public T transform(String s, Class aClass) { + return objectMapper.convertValue(s, aClass); + } + @Override + public T transform(Map map, Class aClass, TableCellByTypeTransformer tableCellByTypeTransformer) { + return objectMapper.convertValue(map, aClass); + } } } diff --git a/examples/spring-txn/src/test/java/io/cucumber/examples/spring/txn/UserSteps.java b/examples/spring-txn/src/test/java/io/cucumber/examples/spring/txn/UserStepdefs.java similarity index 85% rename from examples/spring-txn/src/test/java/io/cucumber/examples/spring/txn/UserSteps.java rename to examples/spring-txn/src/test/java/io/cucumber/examples/spring/txn/UserStepdefs.java index 122d133a45..39f1d0dc2f 100644 --- a/examples/spring-txn/src/test/java/io/cucumber/examples/spring/txn/UserSteps.java +++ b/examples/spring-txn/src/test/java/io/cucumber/examples/spring/txn/UserStepdefs.java @@ -8,7 +8,7 @@ import static org.junit.Assert.assertTrue; -public class UserSteps { +public class UserStepdefs { @Autowired private UserRepository userRepository; @@ -17,14 +17,15 @@ public class UserSteps { private User user; - private void thereIsAUser() { - user = new User("testuser"); + public void thereIsAuser() { + user = new User(); + user.setUsername("testuser"); userRepository.save(user); } @Given("a User has posted the following messages:") public void a_User_has_posted_the_following_messages(List messages) throws Throwable { - thereIsAUser(); + thereIsAuser(); for (Message m : messages) { m.setAuthor(user); messageRepository.save(m); diff --git a/guice/README.md b/guice/README.md index 88bee86bdc..c9a8089c2e 100644 --- a/guice/README.md +++ b/guice/README.md @@ -6,4 +6,4 @@ migration section if upgrading from earlier versions of Cucumber Guice. [Read package documentation online at api.cucumber.io](https://github.com/cucumber/api.cucumber.io) -[Read package documentation offline (raw html)](src/main/java/cucumber/api/guice/package.html) +[Read package documentation offline (raw html)](src/main/java/io/cucumber/guice/api/package.html) diff --git a/guice/pom.xml b/guice/pom.xml index 87b3fcc8db..6feac67cd0 100644 --- a/guice/pom.xml +++ b/guice/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 4.7.2-SNAPSHOT + 5.0.0-SNAPSHOT cucumber-guice @@ -21,6 +21,10 @@ io.cucumber cucumber-java + + org.apiguardian + apiguardian-api + com.google.inject guice @@ -33,10 +37,24 @@ cucumber-junit test + + + org.hamcrest + hamcrest-library + test + + + + org.junit.jupiter + junit-jupiter-api + test + + - junit - junit + org.junit.vintage + junit-vintage-engine test + diff --git a/guice/src/main/java/cucumber/api/guice/README.java b/guice/src/main/java/cucumber/api/guice/README.java deleted file mode 100644 index 1854d00888..0000000000 --- a/guice/src/main/java/cucumber/api/guice/README.java +++ /dev/null @@ -1,8 +0,0 @@ -package cucumber.api.guice; - - -/** - * Please see package documentation in package.html - */ -public class README { -} diff --git a/guice/src/main/java/cucumber/api/guice/package.html b/guice/src/main/java/cucumber/api/guice/package.html deleted file mode 100644 index 163e90fe92..0000000000 --- a/guice/src/main/java/cucumber/api/guice/package.html +++ /dev/null @@ -1,150 +0,0 @@ - -

- This module allows you to use Google Guice dependency injection in your Cucumber tests. Guice comes as standard with - singleton scope and 'no scope'. This module adds Cucumber scenario scope to the scopes available for use in your - test code. The rest of this documentation assumes you have at least a basic understanding of Guice. Please refer to - the Guice wiki if necessary, see - https://github.com/google/guice/wiki/Motivation -

-

About scopes, injectors and migration from earlier versions

-

- It's important to realise the differences in how this module functions when compared with earlier versions. The - changes are as follows. -

-

Version 1.1.7 and earlier

-

- A Guice injector is created at the start of each test scenario and is destroyed at the end of each test scenario. - There is no scenario scope, just singleton and 'no scope'. -

-

Version 1.1.8 onwards

-

- A Guice injector is created once before any tests are run and is destroyed after the last test has run. Before each - test scenario a new scenario scope is created. At the end of the test scenario the scenario scope is destroyed. - Singleton scope exists throughout all test scenarios. -

-

Migrating to version 1.1.8 or later

-

- Users wishing to migrate should replace @Singleton annotations with @ScenarioScope - annotations. Guice modules should also have their singleton bindings updated. All bindings in - Scopes.SINGLETON should be replaced with bindings in CucumberScopes.SCENARIO. -

-

Using the module

-

- By including the cucumber-guice jar on your CLASSPATH your Step Definitions will be - instantiated by Guice. There are two main modes of using the module: with scope annotations and with module - bindings. The two modes can also be mixed. When mixing modes it is important to realise that binding a class in a - scope in a module takes precedence if the same class is also bound using a scope annotation. -

-

Scoping your step definitions

-

- Usually you will want to bind your step definition classes in either scenario scope or in singleton scope. It is not - recommended to leave your step definition classes with no scope as it means that Cucumber will instantiate a new - instance of the class for each step within a scenario that uses that step definition. -

-

Scenario scope

-

- Cucumber will create exactly one instance of a class bound in scenario scope for each scenario in which it is used. - You should use scenario scope when you want to store state during a scenario but do not want the state to interfere - with subsequent scenarios. -

-

-

Singleton scope

-

- Cucumber will create just one instance of a class bound in singleton scope that will last for the lifetime of all - test scenarios in the test run. You should use singleton scope if your classes are stateless. You can also use - singleton scope when your classes contain state but with caution. You should be absolutely sure that a state change - in one scenario could not possibly influence the success or failure of a subsequent scenario. As an example of when - you might use a singleton, imagine you have an http client that is expensive to create. By holding a reference to - the client in a class bound in singleton scope you can reuse the client in multiple scenarios. -

-

Using scope annotations

-

- This is the easy route if you're new to Guice. To bind a class in scenario scope add the - cucumber.runtime.java.guice.ScenarioScoped annotation to the class definition. The class should have - a no-args constructor or one constructor that is annotated with javax.inject.Inject. For example: -

-
-        import cucumber.runtime.java.guice.ScenarioScoped;
-        import javax.inject.Inject;
-
-        {@literal @}ScenarioScoped
-        public class ScenarioScopedSteps {
-
-            private final Object someInjectedDependency;
-
-            {@literal @}Inject
-            public ScenarioScopedSteps(Object someInjectedDependency) {
-                this.someInjectedDependency = someInjectedDependency;
-            }
-
-            ...
-        }
-    
-

- To bind a class in singleton scope add the javax.inject.Singleton annotation to the class definition. - One strategy for using stateless step definitions is to use providers to share stateful scenario scoped instances - between stateless singleton step definition instances. For example: -

-
-        import javax.inject.Inject;
-        import javax.inject.Singleton;
-
-        {@literal @}Singleton
-        public class MyStatelessSteps {
-
-            private final Provider<MyStatefulObject> providerMyStatefulObject;
-
-            {@literal @}Inject
-            public MyStatelessSteps(Provider<MyStatefulObject> providerMyStatefulObject) {
-                this.providerMyStatefulObject = providerMyStatefulObject;
-            }
-
-            {@literal @}Given("^I have (\\d+) cukes in my belly$")
-            public void I_have_cukes_in_my_belly(int n) {
-                providerMyStatefulObject.get().iHaveCukesInMyBelly(n);
-            }
-
-            ...
-        }
-    
-

- There is an alternative explanation of using - - providers for mixing scopes on the Guice wiki. -

-

Using module bindings

-

- As an alternative to using annotations you may prefer to declare Guice bindings in a class that implements - com.google.inject.Module. To do this you should create a class that implements - cucumber.runtime.java.guice.InjectorSource. This gives you complete control over how you obtain a - Guice injector and it's Guice modules. The injector must provide a binding for - cucumber.runtime.java.guice.ScenarioScope. It should also provide a binding for the - cucumber.runtime.java.guice.ScenarioScoped annotation if your classes are using the annotation. The - easiest way to do this it to use CucumberModules.createScenarioModule(). For example: -

-
-        import com.google.inject.Guice;
-        import com.google.inject.Injector;
-        import com.google.inject.Stage;
-        import cucumber.api.guice.CucumberModules;
-        import cucumber.runtime.java.guice.InjectorSource;
-
-        public class YourInjectorSource implements InjectorSource {
-
-            {@literal @}Override
-            public Injector getInjector() {
-                return Guice.createInjector(Stage.PRODUCTION, CucumberModules.createScenarioModule(), new YourModule());
-            }
-        }
-    
-

- Cucumber needs to know where to find the cucumber.runtime.java.guice.InjectorSource that it will use. - You should create a properties file called cucumber.properties and place it in the root of the - classpath. The file should contain a single property key called guice.injector-source with a value - equal to the fully qualified name of the cucumber.runtime.java.guice.InjectorSource. For example: -

-
-        guice.injector-source=com.company.YourInjectorSource
-    
- diff --git a/guice/src/main/java/cucumber/runtime/java/guice/InjectorSource.java b/guice/src/main/java/cucumber/runtime/java/guice/InjectorSource.java deleted file mode 100644 index 5f5a9d88d7..0000000000 --- a/guice/src/main/java/cucumber/runtime/java/guice/InjectorSource.java +++ /dev/null @@ -1,12 +0,0 @@ -package cucumber.runtime.java.guice; - -import com.google.inject.Injector; - -/** - * An implentation of this interface is used to obtain an com.google.inject.Injector that is used to - * provide instances of all the classes that are used to run the Cucumber tests. The injector should be configured with - * a binding for cucumber.runtime.java.guice.ScenarioScope. - */ -public interface InjectorSource { - Injector getInjector(); -} diff --git a/guice/src/main/java/cucumber/runtime/java/guice/impl/InjectorSourceInstantiationFailed.java b/guice/src/main/java/cucumber/runtime/java/guice/impl/InjectorSourceInstantiationFailed.java deleted file mode 100644 index 75136939cb..0000000000 --- a/guice/src/main/java/cucumber/runtime/java/guice/impl/InjectorSourceInstantiationFailed.java +++ /dev/null @@ -1,10 +0,0 @@ -package cucumber.runtime.java.guice.impl; - -import cucumber.runtime.CucumberException; - -public class InjectorSourceInstantiationFailed extends CucumberException { - - public InjectorSourceInstantiationFailed(String message, Throwable cause) { - super(message, cause); - } -} \ No newline at end of file diff --git a/guice/src/main/java/cucumber/api/guice/CucumberModules.java b/guice/src/main/java/io/cucumber/guice/CucumberModules.java similarity index 80% rename from guice/src/main/java/cucumber/api/guice/CucumberModules.java rename to guice/src/main/java/io/cucumber/guice/CucumberModules.java index 424ae6a875..8df0e57394 100644 --- a/guice/src/main/java/cucumber/api/guice/CucumberModules.java +++ b/guice/src/main/java/io/cucumber/guice/CucumberModules.java @@ -1,15 +1,19 @@ -package cucumber.api.guice; +package io.cucumber.guice; import com.google.inject.Module; -import cucumber.runtime.java.guice.ScenarioScope; -import cucumber.runtime.java.guice.ScenarioScoped; -import cucumber.runtime.java.guice.impl.ScenarioModule; +import org.apiguardian.api.API; /** * Provides a convenient {@link Module} instance that contains bindings for * {@link ScenarioScoped} annotation and for {@link ScenarioScope}. */ -public class CucumberModules { +@API(status = API.Status.STABLE) +public final class CucumberModules { + + private CucumberModules(){ + + } + /** * A convenient instance of {@link Module}. Should only be used * in combination with {@link CucumberScopes#SCENARIO}. diff --git a/guice/src/main/java/cucumber/api/guice/CucumberScopes.java b/guice/src/main/java/io/cucumber/guice/CucumberScopes.java similarity index 82% rename from guice/src/main/java/cucumber/api/guice/CucumberScopes.java rename to guice/src/main/java/io/cucumber/guice/CucumberScopes.java index fafe7c6ba5..4aef3db084 100644 --- a/guice/src/main/java/cucumber/api/guice/CucumberScopes.java +++ b/guice/src/main/java/io/cucumber/guice/CucumberScopes.java @@ -1,9 +1,7 @@ -package cucumber.api.guice; +package io.cucumber.guice; import com.google.inject.Module; -import cucumber.runtime.java.guice.ScenarioScope; -import cucumber.runtime.java.guice.ScenarioScoped; -import cucumber.runtime.java.guice.impl.SequentialScenarioScope; +import org.apiguardian.api.API; /** * Creates an instance of {@link ScenarioScope} for use when declaring bindings @@ -14,7 +12,13 @@ * * bind(ScenarioScopedObject.class).in(ScenarioScoped.class); */ -public class CucumberScopes { +@API(status = API.Status.STABLE) +public final class CucumberScopes { + + private CucumberScopes(){ + + } + /** * A convenient instance of {@link ScenarioScope}. Should only be used * in combination with {@link CucumberModules#SCENARIO}. diff --git a/guice/src/main/java/cucumber/runtime/java/guice/impl/GuiceFactory.java b/guice/src/main/java/io/cucumber/guice/GuiceFactory.java similarity index 66% rename from guice/src/main/java/cucumber/runtime/java/guice/impl/GuiceFactory.java rename to guice/src/main/java/io/cucumber/guice/GuiceFactory.java index 87bdd8ee6b..9317be8279 100644 --- a/guice/src/main/java/cucumber/runtime/java/guice/impl/GuiceFactory.java +++ b/guice/src/main/java/io/cucumber/guice/GuiceFactory.java @@ -1,26 +1,27 @@ -package cucumber.runtime.java.guice.impl; +package io.cucumber.guice; import com.google.inject.Injector; -import cucumber.runtime.Env; -import cucumber.runtime.java.guice.ScenarioScope; import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.options.CucumberProperties; +import org.apiguardian.api.API; /** - * Guice implementation of the cucumber.api.java.ObjectFactory. + * Guice implementation of the io.cucumber.core.backend.ObjectFactory. */ -public class GuiceFactory implements ObjectFactory { +@API(status = API.Status.STABLE) +public final class GuiceFactory implements ObjectFactory { private final Injector injector; public GuiceFactory() { - this(new InjectorSourceFactory(Env.INSTANCE).create().getInjector()); + this(new InjectorSourceFactory(CucumberProperties.create()).create().getInjector()); } /** * Package private constructor that is called by the public constructor at runtime and is also called directly by * tests. * - * @param injector an injector configured with a binding for cucumber.runtime.java.guice.ScenarioScope. + * @param injector an injector configured with a binding for ScenarioScope. */ GuiceFactory(Injector injector) { this.injector = injector; diff --git a/guice/src/main/java/io/cucumber/guice/InjectorSource.java b/guice/src/main/java/io/cucumber/guice/InjectorSource.java new file mode 100644 index 0000000000..db5cf11841 --- /dev/null +++ b/guice/src/main/java/io/cucumber/guice/InjectorSource.java @@ -0,0 +1,14 @@ +package io.cucumber.guice; + +import com.google.inject.Injector; +import org.apiguardian.api.API; + +/** + * An implementation of this interface is used to obtain an com.google.inject.Injector that is used to + * provide instances of all the classes that are used to run the Cucumber tests. The injector should be configured with + * a binding for ScenarioScope. + */ +@API(status = API.Status.STABLE) +public interface InjectorSource { + Injector getInjector(); +} diff --git a/guice/src/main/java/cucumber/runtime/java/guice/impl/InjectorSourceFactory.java b/guice/src/main/java/io/cucumber/guice/InjectorSourceFactory.java similarity index 57% rename from guice/src/main/java/cucumber/runtime/java/guice/impl/InjectorSourceFactory.java rename to guice/src/main/java/io/cucumber/guice/InjectorSourceFactory.java index 59c4dbc9f4..7dba40f02f 100644 --- a/guice/src/main/java/cucumber/runtime/java/guice/impl/InjectorSourceFactory.java +++ b/guice/src/main/java/io/cucumber/guice/InjectorSourceFactory.java @@ -1,24 +1,23 @@ -package cucumber.runtime.java.guice.impl; +package io.cucumber.guice; import com.google.inject.Guice; -import com.google.inject.Injector; import com.google.inject.Stage; -import cucumber.runtime.Env; -import cucumber.runtime.java.guice.InjectorSource; + +import java.util.Map; import static java.text.MessageFormat.format; -public class InjectorSourceFactory { +final class InjectorSourceFactory { - public static final String GUICE_INJECTOR_SOURCE_KEY = "guice.injector-source"; - private final Env env; + static final String GUICE_INJECTOR_SOURCE_KEY = "guice.injector-source"; + private final Map properties; - public InjectorSourceFactory(Env env) { - this.env = env; + InjectorSourceFactory(Map properties) { + this.properties = properties; } - public InjectorSource create() { - String injectorSourceClassName = env.get(GUICE_INJECTOR_SOURCE_KEY); + InjectorSource create() { + String injectorSourceClassName = properties.get(GUICE_INJECTOR_SOURCE_KEY); if (injectorSourceClassName == null) { return createDefaultScenarioModuleInjectorSource(); } else { @@ -27,13 +26,7 @@ public InjectorSource create() { } private InjectorSource createDefaultScenarioModuleInjectorSource() { - return new InjectorSource() { - @Override - public Injector getInjector() { - ScenarioModule scenarioModule = new ScenarioModule(new SequentialScenarioScope()); - return Guice.createInjector(Stage.PRODUCTION, scenarioModule); - } - }; + return () -> Guice.createInjector(Stage.PRODUCTION, CucumberModules.SCENARIO); } private InjectorSource instantiateUserSpecifiedInjectorSource(String injectorSourceClassName) { diff --git a/guice/src/main/java/io/cucumber/guice/InjectorSourceInstantiationFailed.java b/guice/src/main/java/io/cucumber/guice/InjectorSourceInstantiationFailed.java new file mode 100644 index 0000000000..5befc8f9da --- /dev/null +++ b/guice/src/main/java/io/cucumber/guice/InjectorSourceInstantiationFailed.java @@ -0,0 +1,10 @@ +package io.cucumber.guice; + +import io.cucumber.core.exception.CucumberException; + +class InjectorSourceInstantiationFailed extends CucumberException { + + InjectorSourceInstantiationFailed(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/guice/src/main/java/cucumber/runtime/java/guice/impl/ScenarioModule.java b/guice/src/main/java/io/cucumber/guice/ScenarioModule.java similarity index 78% rename from guice/src/main/java/cucumber/runtime/java/guice/impl/ScenarioModule.java rename to guice/src/main/java/io/cucumber/guice/ScenarioModule.java index c5e7bf646a..4dbe7e4aac 100644 --- a/guice/src/main/java/cucumber/runtime/java/guice/impl/ScenarioModule.java +++ b/guice/src/main/java/io/cucumber/guice/ScenarioModule.java @@ -1,8 +1,6 @@ -package cucumber.runtime.java.guice.impl; +package io.cucumber.guice; import com.google.inject.AbstractModule; -import cucumber.runtime.java.guice.ScenarioScoped; -import cucumber.runtime.java.guice.ScenarioScope; public class ScenarioModule extends AbstractModule { diff --git a/guice/src/main/java/cucumber/runtime/java/guice/ScenarioScope.java b/guice/src/main/java/io/cucumber/guice/ScenarioScope.java similarity index 74% rename from guice/src/main/java/cucumber/runtime/java/guice/ScenarioScope.java rename to guice/src/main/java/io/cucumber/guice/ScenarioScope.java index 405c8389be..4db67b6647 100644 --- a/guice/src/main/java/cucumber/runtime/java/guice/ScenarioScope.java +++ b/guice/src/main/java/io/cucumber/guice/ScenarioScope.java @@ -1,11 +1,13 @@ -package cucumber.runtime.java.guice; +package io.cucumber.guice; import com.google.inject.Scope; +import org.apiguardian.api.API; /** * A custom Guice scope that enables classes to be bound in a scope that will last for the lifetime of one Cucumber * scenario. */ +@API(status = API.Status.STABLE) public interface ScenarioScope extends Scope { void enterScope(); void exitScope(); diff --git a/guice/src/main/java/cucumber/runtime/java/guice/ScenarioScoped.java b/guice/src/main/java/io/cucumber/guice/ScenarioScoped.java similarity index 62% rename from guice/src/main/java/cucumber/runtime/java/guice/ScenarioScoped.java rename to guice/src/main/java/io/cucumber/guice/ScenarioScoped.java index f11119e081..b615133e27 100644 --- a/guice/src/main/java/cucumber/runtime/java/guice/ScenarioScoped.java +++ b/guice/src/main/java/io/cucumber/guice/ScenarioScoped.java @@ -1,6 +1,7 @@ -package cucumber.runtime.java.guice; +package io.cucumber.guice; import com.google.inject.ScopeAnnotation; +import org.apiguardian.api.API; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -11,7 +12,11 @@ /** * A custom Guice scope annotation that is usually bound to an instance of - * cucumber.runtime.java.guice.ScenarioScope. + * ScenarioScope. */ -@Target({ TYPE, METHOD }) @Retention(RUNTIME) @ScopeAnnotation -public @interface ScenarioScoped {} \ No newline at end of file +@Target({TYPE, METHOD}) +@Retention(RUNTIME) +@ScopeAnnotation +@API(status = API.Status.STABLE) +public @interface ScenarioScoped { +} \ No newline at end of file diff --git a/guice/src/main/java/cucumber/runtime/java/guice/impl/SequentialScenarioScope.java b/guice/src/main/java/io/cucumber/guice/SequentialScenarioScope.java similarity index 85% rename from guice/src/main/java/cucumber/runtime/java/guice/impl/SequentialScenarioScope.java rename to guice/src/main/java/io/cucumber/guice/SequentialScenarioScope.java index b0b370d1cd..fdc203d895 100644 --- a/guice/src/main/java/cucumber/runtime/java/guice/impl/SequentialScenarioScope.java +++ b/guice/src/main/java/io/cucumber/guice/SequentialScenarioScope.java @@ -1,21 +1,20 @@ -package cucumber.runtime.java.guice.impl; +package io.cucumber.guice; import com.google.inject.Key; import com.google.inject.OutOfScopeException; import com.google.inject.Provider; -import cucumber.runtime.java.guice.ScenarioScope; import java.util.HashMap; import java.util.Map; -public class SequentialScenarioScope implements ScenarioScope { +class SequentialScenarioScope implements ScenarioScope { /** * Scopes a provider. The returned provider returns objects from this scope. * If an object does not exist in this scope, the provider can use the given * unscoped provider to retrieve one. - *

- *

Scope implementations are strongly encouraged to override + *

+ * Scope implementations are strongly encouraged to override * {@link Object#toString} in the returned provider and include the backing * provider's {@code toString()} output. * @@ -23,8 +22,8 @@ public class SequentialScenarioScope implements ScenarioScope { * @param unscoped locates an instance when one doesn't already exist in this * scope. * @return a new provider which only delegates to the given unscoped provider - * when an instance of the requested object doesn't already exist in this - * scope + * when an instance of the requested object doesn't already exist in this + * scope */ @Override public Provider scope(final Key key, final Provider unscoped) { diff --git a/guice/src/main/java/io/cucumber/guice/package-info.java b/guice/src/main/java/io/cucumber/guice/package-info.java new file mode 100644 index 0000000000..6b218582d2 --- /dev/null +++ b/guice/src/main/java/io/cucumber/guice/package-info.java @@ -0,0 +1,156 @@ +/** + * Cucumber Guice configuration Api + *

+ * An implentation of this interface is used to obtain an com.google.inject.Injector that is used to + * provide instances of all the classes that are used to run the Cucumber tests. The injector should be configured with + * a binding for ScenarioScope. + *

+ * This module allows you to use Google Guice dependency injection in your Cucumber tests. Guice comes as standard with + * singleton scope and 'no scope'. This module adds Cucumber scenario scope to the scopes available for use in your + * test code. The rest of this documentation assumes you have at least a basic understanding of Guice. Please refer to + * the Guice wiki if necessary, see + * https://github.com/google/guice/wiki/Motivation + *

+ *

About scopes, injectors and migration from earlier versions

+ *

+ * It's important to realise the differences in how this module functions when compared with earlier versions. The + * changes are as follows. + *

+ *

Version 1.1.7 and earlier

+ *

+ * A Guice injector is created at the start of each test scenario and is destroyed at the end of each test scenario. + * There is no scenario scope, just singleton and 'no scope'. + *

+ *

Version 1.1.8 onwards

+ *

+ * A Guice injector is created once before any tests are run and is destroyed after the last test has run. Before each + * test scenario a new scenario scope is created. At the end of the test scenario the scenario scope is destroyed. + * Singleton scope exists throughout all test scenarios. + *

+ *

Migrating to version 1.1.8 or later

+ *

+ * Users wishing to migrate should replace @Singleton annotations with @ScenarioScope + * annotations. Guice modules should also have their singleton bindings updated. All bindings in + * Scopes.SINGLETON should be replaced with bindings in CucumberScopes.SCENARIO. + *

+ *

Using the module

+ *

+ * By including the cucumber-guice jar on your CLASSPATH your Step Definitions will be + * instantiated by Guice. There are two main modes of using the module: with scope annotations and with module + * bindings. The two modes can also be mixed. When mixing modes it is important to realise that binding a class in a + * scope in a module takes precedence if the same class is also bound using a scope annotation. + *

+ *

Scoping your step definitions

+ *

+ * Usually you will want to bind your step definition classes in either scenario scope or in singleton scope. It is not + * recommended to leave your step definition classes with no scope as it means that Cucumber will instantiate a new + * instance of the class for each step within a scenario that uses that step definition. + *

+ *

Scenario scope

+ *

+ * Cucumber will create exactly one instance of a class bound in scenario scope for each scenario in which it is used. + * You should use scenario scope when you want to store state during a scenario but do not want the state to interfere + * with subsequent scenarios. + *

+ *

Singleton scope

+ *

+ * Cucumber will create just one instance of a class bound in singleton scope that will last for the lifetime of all + * test scenarios in the test run. You should use singleton scope if your classes are stateless. You can also use + * singleton scope when your classes contain state but with caution. You should be absolutely sure that a state change + * in one scenario could not possibly influence the success or failure of a subsequent scenario. As an example of when + * you might use a singleton, imagine you have an http client that is expensive to create. By holding a reference to + * the client in a class bound in singleton scope you can reuse the client in multiple scenarios. + *

+ *

Using scope annotations

+ *

+ * This is the easy route if you're new to Guice. To bind a class in scenario scope add the + * io.cucumber.guice.ScenarioScoped annotation to the class definition. The class should have + * a no-args constructor or one constructor that is annotated with javax.inject.Inject. For example: + *

+ *
+ * import cucumber.runtime.java.guice.ScenarioScoped;
+ * import javax.inject.Inject;
+ *
+ * {@literal @}ScenarioScoped
+ * public class ScenarioScopedSteps {
+ *
+ * private final Object someInjectedDependency;
+ *
+ * {@literal @}Inject
+ * public ScenarioScopedSteps(Object someInjectedDependency) {
+ * this.someInjectedDependency = someInjectedDependency;
+ * }
+ *
+ * ...
+ * }
+ * 
+ *

+ * To bind a class in singleton scope add the javax.inject.Singleton annotation to the class definition. + * One strategy for using stateless step definitions is to use providers to share stateful scenario scoped instances + * between stateless singleton step definition instances. For example: + *

+ *
+ * import javax.inject.Inject;
+ * import javax.inject.Singleton;
+ *
+ * {@literal @}Singleton
+ * public class MyStatelessSteps {
+ *
+ * private final Provider<MyStatefulObject> providerMyStatefulObject;
+ *
+ * {@literal @}Inject
+ * public MyStatelessSteps(Provider<MyStatefulObject> providerMyStatefulObject) {
+ * this.providerMyStatefulObject = providerMyStatefulObject;
+ * }
+ *
+ * {@literal @}Given("^I have (\\d+) cukes in my belly$")
+ * public void I_have_cukes_in_my_belly(int n) {
+ * providerMyStatefulObject.get().iHaveCukesInMyBelly(n);
+ * }
+ *
+ * ...
+ * }
+ * 
+ *

+ * There is an alternative explanation of using + * + * providers for mixing scopes on the Guice wiki. + *

+ *

Using module bindings

+ *

+ * As an alternative to using annotations you may prefer to declare Guice bindings in a class that implements + * com.google.inject.Module. To do this you should create a class that implements + * io.cucumber.guice.api.InjectorSource. This gives you complete control over how you obtain a + * Guice injector and it's Guice modules. The injector must provide a binding for + * io.cucumber.guice.ScenarioScope. It should also provide a binding for the + * io.cucumber.guice.ScenarioScoped annotation if your classes are using the annotation. The + * easiest way to do this it to use CucumberModules.createScenarioModule(). For example: + *

+ *
+ * import com.google.inject.Guice;
+ * import com.google.inject.Injector;
+ * import com.google.inject.Stage;
+ * import cucumber.api.guice.CucumberModules;
+ * import cucumber.runtime.java.guice.InjectorSource;
+ *
+ * public class YourInjectorSource implements InjectorSource {
+ *
+ * {@literal @}Override
+ * public Injector getInjector() {
+ * return Guice.createInjector(Stage.PRODUCTION, CucumberModules.createScenarioModule(), new YourModule());
+ * }
+ * }
+ * 
+ *

+ * Cucumber needs to know where to find the io.cucumber.guice.api.InjectorSource that it will use. + * You should create a properties file called {@value io.cucumber.core.options.Constants#CUCUMBER_PROPERTIES_FILE_NAME} + * and place it in the root of the classpath. The file should contain a single property key called + * guice.injector-source with a value equal to the fully qualified name of the + * io.cucumber.guice.api.InjectorSource. For example: + *

+ *
+ * guice.injector-source=com.company.YourInjectorSource
+ * 
+ */ +package io.cucumber.guice; diff --git a/guice/src/main/resources/META-INF/services/io.cucumber.core.backend.ObjectFactory b/guice/src/main/resources/META-INF/services/io.cucumber.core.backend.ObjectFactory index d8103b610e..9ec8b3e205 100644 --- a/guice/src/main/resources/META-INF/services/io.cucumber.core.backend.ObjectFactory +++ b/guice/src/main/resources/META-INF/services/io.cucumber.core.backend.ObjectFactory @@ -1 +1 @@ -cucumber.runtime.java.guice.impl.GuiceFactory \ No newline at end of file +io.cucumber.guice.GuiceFactory \ No newline at end of file diff --git a/guice/src/test/java/cucumber/runtime/java/guice/integration/ScenarioScopedObject.java b/guice/src/test/java/cucumber/runtime/java/guice/integration/ScenarioScopedObject.java deleted file mode 100644 index 5462ee1b16..0000000000 --- a/guice/src/test/java/cucumber/runtime/java/guice/integration/ScenarioScopedObject.java +++ /dev/null @@ -1,4 +0,0 @@ -package cucumber.runtime.java.guice.integration; - -public class ScenarioScopedObject { -} diff --git a/guice/src/test/java/cucumber/runtime/java/guice/integration/SingletonObject.java b/guice/src/test/java/cucumber/runtime/java/guice/integration/SingletonObject.java deleted file mode 100644 index 0c55f1ace3..0000000000 --- a/guice/src/test/java/cucumber/runtime/java/guice/integration/SingletonObject.java +++ /dev/null @@ -1,4 +0,0 @@ -package cucumber.runtime.java.guice.integration; - -public class SingletonObject { -} diff --git a/guice/src/test/java/cucumber/runtime/java/guice/integration/UnScopedObject.java b/guice/src/test/java/cucumber/runtime/java/guice/integration/UnScopedObject.java deleted file mode 100644 index 6da9e483b8..0000000000 --- a/guice/src/test/java/cucumber/runtime/java/guice/integration/UnScopedObject.java +++ /dev/null @@ -1,4 +0,0 @@ -package cucumber.runtime.java.guice.integration; - -public class UnScopedObject { -} diff --git a/guice/src/test/java/cucumber/runtime/java/guice/impl/GuiceFactoryTest.java b/guice/src/test/java/io/cucumber/guice/GuiceFactoryTest.java similarity index 76% rename from guice/src/test/java/cucumber/runtime/java/guice/impl/GuiceFactoryTest.java rename to guice/src/test/java/io/cucumber/guice/GuiceFactoryTest.java index b70e41cc8a..db841736c0 100644 --- a/guice/src/test/java/cucumber/runtime/java/guice/impl/GuiceFactoryTest.java +++ b/guice/src/test/java/io/cucumber/guice/GuiceFactoryTest.java @@ -1,31 +1,33 @@ -package cucumber.runtime.java.guice.impl; +package io.cucumber.guice; import com.google.inject.AbstractModule; -import com.google.inject.ConfigurationException; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Module; import com.google.inject.Scopes; import com.google.inject.Stage; -import cucumber.api.guice.CucumberModules; -import cucumber.runtime.java.guice.ScenarioScoped; +import io.cucumber.guice.matcher.ElementsAreAllEqualMatcher; +import io.cucumber.guice.matcher.ElementsAreAllUniqueMatcher; import io.cucumber.core.backend.ObjectFactory; import org.junit.After; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import javax.inject.Singleton; import java.util.Arrays; import java.util.List; -import static cucumber.runtime.java.guice.matcher.ElementsAreAllEqualMatcher.elementsAreAllEqual; -import static cucumber.runtime.java.guice.matcher.ElementsAreAllUniqueMatcher.elementsAreAllUnique; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; +import static org.junit.rules.ExpectedException.none; public class GuiceFactoryTest { + @Rule + public ExpectedException expectedException = none(); + private ObjectFactory factory; private List instancesFromSameScenario; private List instancesFromDifferentScenarios; @@ -37,7 +39,7 @@ public void tearDown() { } @Test - public void factoryCanBeIntantiatedWithDefaultConstructor() throws Exception { + public void factoryCanBeIntantiatedWithDefaultConstructor() { factory = new GuiceFactory(); assertThat(factory, notNullValue()); } @@ -51,67 +53,69 @@ public void factoryCanBeIntantiatedWithArgConstructor() { @Test public void factoryStartFailsIfScenarioScopeIsNotBound() { factory = new GuiceFactory(Guice.createInjector()); - try { - factory.start(); - fail(); - } catch (ConfigurationException e) { - assertThat(e.getMessage(), - containsString("No implementation for cucumber.runtime.java.guice.ScenarioScope was bound")); - } + expectedException.expectMessage(containsString("No implementation for io.cucumber.guice.ScenarioScope was bound")); + factory.start(); } - static class UnscopedClass {} + static class UnscopedClass { + } @Test public void shouldGiveNewInstancesOfUnscopedClassWithinAScenario() { factory = new GuiceFactory(injector(CucumberModules.createScenarioModule())); instancesFromSameScenario = getInstancesFromSameScenario(factory, UnscopedClass.class); - assertThat(instancesFromSameScenario, elementsAreAllUnique()); + assertThat(instancesFromSameScenario, ElementsAreAllUniqueMatcher.elementsAreAllUnique()); } @Test public void shouldGiveNewInstanceOfUnscopedClassForEachScenario() { factory = new GuiceFactory(injector(CucumberModules.createScenarioModule())); instancesFromDifferentScenarios = getInstancesFromDifferentScenarios(factory, UnscopedClass.class); - assertThat(instancesFromDifferentScenarios, elementsAreAllUnique()); + assertThat(instancesFromDifferentScenarios, ElementsAreAllUniqueMatcher.elementsAreAllUnique()); } - @ScenarioScoped static class AnnotatedScenarioScopedClass {} + @ScenarioScoped + static class AnnotatedScenarioScopedClass { + } @Test public void shouldGiveTheSameInstanceOfAnnotatedScenarioScopedClassWithinAScenario() { factory = new GuiceFactory(injector(CucumberModules.createScenarioModule())); instancesFromSameScenario = getInstancesFromSameScenario(factory, AnnotatedScenarioScopedClass.class); - assertThat(instancesFromSameScenario, elementsAreAllEqual()); + assertThat(instancesFromSameScenario, ElementsAreAllEqualMatcher.elementsAreAllEqual()); } @Test public void shouldGiveNewInstanceOfAnnotatedScenarioScopedClassForEachScenario() { factory = new GuiceFactory(injector(CucumberModules.createScenarioModule())); instancesFromDifferentScenarios = getInstancesFromDifferentScenarios(factory, AnnotatedScenarioScopedClass.class); - assertThat(instancesFromDifferentScenarios, elementsAreAllUnique()); + assertThat(instancesFromDifferentScenarios, ElementsAreAllUniqueMatcher.elementsAreAllUnique()); } - @Singleton static class AnnotatedSingletonClass {} + @Singleton + static class AnnotatedSingletonClass { + } @Test public void shouldGiveTheSameInstanceOfAnnotatedSingletonClassWithinAScenario() { factory = new GuiceFactory(injector(CucumberModules.createScenarioModule())); instancesFromSameScenario = getInstancesFromSameScenario(factory, AnnotatedSingletonClass.class); - assertThat(instancesFromSameScenario, elementsAreAllEqual()); + assertThat(instancesFromSameScenario, ElementsAreAllEqualMatcher.elementsAreAllEqual()); } @Test public void shouldGiveTheSameInstanceOfAnnotatedSingletonClassForEachScenario() { factory = new GuiceFactory(injector(CucumberModules.createScenarioModule())); instancesFromDifferentScenarios = getInstancesFromDifferentScenarios(factory, AnnotatedSingletonClass.class); - assertThat(instancesFromDifferentScenarios, elementsAreAllEqual()); + assertThat(instancesFromDifferentScenarios, ElementsAreAllEqualMatcher.elementsAreAllEqual()); } - static class BoundScenarioScopedClass {} + static class BoundScenarioScopedClass { + } final AbstractModule boundScenarioScopedClassModule = new AbstractModule() { - @Override protected void configure() { + @Override + protected void configure() { bind(BoundScenarioScopedClass.class).in(ScenarioScoped.class); } }; @@ -120,20 +124,22 @@ static class BoundScenarioScopedClass {} public void shouldGiveTheSameInstanceOfBoundScenarioScopedClassWithinAScenario() { factory = new GuiceFactory(injector(CucumberModules.createScenarioModule(), boundScenarioScopedClassModule)); instancesFromSameScenario = getInstancesFromSameScenario(factory, BoundScenarioScopedClass.class); - assertThat(instancesFromSameScenario, elementsAreAllEqual()); + assertThat(instancesFromSameScenario, ElementsAreAllEqualMatcher.elementsAreAllEqual()); } @Test public void shouldGiveNewInstanceOfBoundScenarioScopedClassForEachScenario() { factory = new GuiceFactory(injector(CucumberModules.SCENARIO, boundScenarioScopedClassModule)); instancesFromDifferentScenarios = getInstancesFromDifferentScenarios(factory, BoundScenarioScopedClass.class); - assertThat(instancesFromDifferentScenarios, elementsAreAllUnique()); + assertThat(instancesFromDifferentScenarios, ElementsAreAllUniqueMatcher.elementsAreAllUnique()); } - static class BoundSingletonClass {} + static class BoundSingletonClass { + } final AbstractModule boundSingletonClassModule = new AbstractModule() { - @Override protected void configure() { + @Override + protected void configure() { bind(BoundSingletonClass.class).in(Scopes.SINGLETON); } }; @@ -142,14 +148,14 @@ static class BoundSingletonClass {} public void shouldGiveTheSameInstanceOfBoundSingletonClassWithinAScenario() { factory = new GuiceFactory(injector(CucumberModules.SCENARIO, boundSingletonClassModule)); instancesFromSameScenario = getInstancesFromSameScenario(factory, BoundSingletonClass.class); - assertThat(instancesFromSameScenario, elementsAreAllEqual()); + assertThat(instancesFromSameScenario, ElementsAreAllEqualMatcher.elementsAreAllEqual()); } @Test public void shouldGiveTheSameInstanceOfBoundSingletonClassForEachScenario() { factory = new GuiceFactory(injector(CucumberModules.createScenarioModule(), boundSingletonClassModule)); instancesFromDifferentScenarios = getInstancesFromDifferentScenarios(factory, BoundSingletonClass.class); - assertThat(instancesFromDifferentScenarios, elementsAreAllEqual()); + assertThat(instancesFromDifferentScenarios, ElementsAreAllEqualMatcher.elementsAreAllEqual()); } @Test @@ -161,7 +167,7 @@ protected void configure() { } })); instancesFromDifferentScenarios = getInstancesFromDifferentScenarios(factory, AnnotatedSingletonClass.class); - assertThat(instancesFromDifferentScenarios, elementsAreAllUnique()); + assertThat(instancesFromDifferentScenarios, ElementsAreAllUniqueMatcher.elementsAreAllUnique()); } private Injector injector(Module... module) { diff --git a/guice/src/test/java/cucumber/runtime/java/guice/impl/InjectorSourceFactoryTest.java b/guice/src/test/java/io/cucumber/guice/InjectorSourceFactoryTest.java similarity index 54% rename from guice/src/test/java/cucumber/runtime/java/guice/impl/InjectorSourceFactoryTest.java rename to guice/src/test/java/io/cucumber/guice/InjectorSourceFactoryTest.java index 61e3a84fda..e8a1123554 100644 --- a/guice/src/test/java/cucumber/runtime/java/guice/impl/InjectorSourceFactoryTest.java +++ b/guice/src/test/java/io/cucumber/guice/InjectorSourceFactoryTest.java @@ -1,18 +1,14 @@ -package cucumber.runtime.java.guice.impl; +package io.cucumber.guice; import com.google.inject.Injector; -import cucumber.runtime.Env; -import cucumber.runtime.java.guice.InjectorSource; -import org.junit.Assert; import org.junit.Test; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.Properties; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; @@ -21,13 +17,13 @@ public class InjectorSourceFactoryTest { - private InjectorSourceFactory createInjectorSourceFactory(Properties properties) { - return new InjectorSourceFactory(new Env(properties)); + private InjectorSourceFactory createInjectorSourceFactory(Map properties) { + return new InjectorSourceFactory(properties); } @Test - public void createsDefaultInjectorSourceWhenGuiceModulePropertyIsNotSet() throws Exception { - InjectorSourceFactory injectorSourceFactory = createInjectorSourceFactory(new Properties()); + public void createsDefaultInjectorSourceWhenGuiceModulePropertyIsNotSet() { + InjectorSourceFactory injectorSourceFactory = createInjectorSourceFactory(Collections.emptyMap()); assertThat(injectorSourceFactory.create(), is(instanceOf(InjectorSource.class))); } @@ -39,17 +35,17 @@ public Injector getInjector() { } @Test - public void instantiatesInjectorSourceByFullyQualifiedName() throws Exception { - Properties properties = new Properties(); - properties.setProperty(InjectorSourceFactory.GUICE_INJECTOR_SOURCE_KEY, CustomInjectorSource.class.getName()); + public void instantiatesInjectorSourceByFullyQualifiedName() { + Map properties = new HashMap<>(); + properties.put(InjectorSourceFactory.GUICE_INJECTOR_SOURCE_KEY, CustomInjectorSource.class.getName()); InjectorSourceFactory injectorSourceFactory = createInjectorSourceFactory(properties); assertThat(injectorSourceFactory.create(), is(instanceOf(CustomInjectorSource.class))); } @Test - public void failsToInstantiateNonExistantClass() throws Exception { - Properties properties = new Properties(); - properties.setProperty(InjectorSourceFactory.GUICE_INJECTOR_SOURCE_KEY, "some.bogus.Class"); + public void failsToInstantiateNonExistantClass() { + Map properties = new HashMap<>(); + properties.put(InjectorSourceFactory.GUICE_INJECTOR_SOURCE_KEY, "some.bogus.Class"); InjectorSourceFactory injectorSourceFactory = createInjectorSourceFactory(properties); try { injectorSourceFactory.create(); @@ -60,9 +56,9 @@ public void failsToInstantiateNonExistantClass() throws Exception { } @Test - public void failsToInstantiateClassNotImplementingInjectorSource() throws Exception { - Properties properties = new Properties(); - properties.setProperty(InjectorSourceFactory.GUICE_INJECTOR_SOURCE_KEY, String.class.getName()); + public void failsToInstantiateClassNotImplementingInjectorSource() { + Map properties = new HashMap<>(); + properties.put(InjectorSourceFactory.GUICE_INJECTOR_SOURCE_KEY, String.class.getName()); InjectorSourceFactory injectorSourceFactory = createInjectorSourceFactory(properties); try { injectorSourceFactory.create(); @@ -83,9 +79,9 @@ public Injector getInjector() { } @Test - public void failsToInstantiateClassWithPrivateConstructor() throws Exception { - Properties properties = new Properties(); - properties.setProperty(InjectorSourceFactory.GUICE_INJECTOR_SOURCE_KEY, PrivateConstructor.class.getName()); + public void failsToInstantiateClassWithPrivateConstructor() { + Map properties = new HashMap<>(); + properties.put(InjectorSourceFactory.GUICE_INJECTOR_SOURCE_KEY, PrivateConstructor.class.getName()); InjectorSourceFactory injectorSourceFactory = createInjectorSourceFactory(properties); try { injectorSourceFactory.create(); @@ -106,9 +102,9 @@ public Injector getInjector() { } @Test - public void failsToInstantiateClassWithNoDefaultConstructor() throws Exception { - Properties properties = new Properties(); - properties.setProperty(InjectorSourceFactory.GUICE_INJECTOR_SOURCE_KEY, NoDefaultConstructor.class.getName()); + public void failsToInstantiateClassWithNoDefaultConstructor() { + Map properties = new HashMap<>(); + properties.put(InjectorSourceFactory.GUICE_INJECTOR_SOURCE_KEY, NoDefaultConstructor.class.getName()); InjectorSourceFactory injectorSourceFactory = createInjectorSourceFactory(properties); try { injectorSourceFactory.create(); @@ -118,58 +114,57 @@ public void failsToInstantiateClassWithNoDefaultConstructor() throws Exception { } } - /** - *

Simulates enterprise applications which often use a hierarchy of classloaders. - * - *

MyChildClassLoader is the only classloader with knowledge of c.r.j.guice.impl.LivesInChildClassLoader - * - *

The bytecode of LivesInChildClassLoader is intentionally renamed to 'LivesInChildClassLoader.class.txt' to prevent - * this test's ClassLoader from resolving it. - * - *

If InjectorSourceFactory calls Class#forName without an explicit ClassLoader argument, which is the behavior of - * 1.2.4 and earlier, Class#forName will default to the test's ClassLoader which has no knowledge - * of class LivesInChildClassLoader and the test will fail. - * - *

See https://github.com/cucumber/cucumber-jvm/issues/1036 - * @throws Exception - */ - @Test - public void instantiateClassInChildClassLoader() throws Exception { + /** + *

Simulates enterprise applications which often use a hierarchy of classloaders. + * + *

MyChildClassLoader is the only classloader with knowledge of c.r.j.guice.impl.LivesInChildClassLoader + * + *

The bytecode of LivesInChildClassLoader is intentionally renamed to 'LivesInChildClassLoader.class.bin.txt' to prevent + * this test's ClassLoader from resolving it. + * + *

If InjectorSourceFactory calls Class#forName without an explicit ClassLoader argument, which is the behavior of + * 1.2.4 and earlier, Class#forName will default to the test's ClassLoader which has no knowledge + * of class LivesInChildClassLoader and the test will fail. + * + *

See https://github.com/cucumber/cucumber-jvm/issues/1036 + */ + @Test + public void instantiateClassInChildClassLoader() { ClassLoader childClassLoader = new MyChildClassLoader(this.getClass().getClassLoader()); - Thread.currentThread().setContextClassLoader( childClassLoader ); - - Properties properties = new Properties(); - properties.setProperty(InjectorSourceFactory.GUICE_INJECTOR_SOURCE_KEY, "cucumber.runtime.java.guice.impl.LivesInChildClassLoader"); + Thread.currentThread().setContextClassLoader(childClassLoader); + + Map properties = new HashMap<>(); + properties.put(InjectorSourceFactory.GUICE_INJECTOR_SOURCE_KEY, "io.cucumber.guice.impl.LivesInChildClassLoader"); InjectorSourceFactory injectorSourceFactory = createInjectorSourceFactory(properties); - - assertThat(injectorSourceFactory.create(), is(instanceOf(InjectorSource.class))); + + assertThat(injectorSourceFactory.create(), is(instanceOf(InjectorSource.class))); } - + private static class MyChildClassLoader extends ClassLoader { - public MyChildClassLoader( ClassLoader parent ) { + public MyChildClassLoader(ClassLoader parent) { super(parent); } @Override - protected Class loadClass( String name, boolean resolve ) throws ClassNotFoundException { - if( name.equals( "cucumber.runtime.java.guice.impl.LivesInChildClassLoader" ) ) { - String filename = getClass().getClassLoader().getResource("cucumber/runtime/java/guice/impl/LivesInChildClassLoader.class.bin").getFile(); - File file = new File( filename ); + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (name.equals("io.cucumber.guice.impl.LivesInChildClassLoader")) { + String filename = getClass().getClassLoader().getResource("io/cucumber/guice/impl/LivesInChildClassLoader.class.bin").getFile(); + File file = new File(filename); try { - FileInputStream in = new FileInputStream( file ); + FileInputStream in = new FileInputStream(file); byte[] bytes = new byte[1024]; ByteArrayOutputStream content = new ByteArrayOutputStream(); - while( true ) { + while (true) { int iLen = in.read(bytes); - content.write( bytes, 0, iLen ); - if( iLen < 1024 ) { + content.write(bytes, 0, iLen); + if (iLen < 1024) { break; } } byte[] bytecode = content.toByteArray(); - return defineClass( name, bytecode, 0, bytecode.length ); + return defineClass(name, bytecode, 0, bytecode.length); } catch (Exception e) { - throw new RuntimeException( e ); + throw new RuntimeException(e); } } return super.loadClass(name, resolve); diff --git a/guice/src/test/java/cucumber/runtime/java/guice/collection/CollectionUtil.java b/guice/src/test/java/io/cucumber/guice/collection/CollectionUtil.java similarity index 94% rename from guice/src/test/java/cucumber/runtime/java/guice/collection/CollectionUtil.java rename to guice/src/test/java/io/cucumber/guice/collection/CollectionUtil.java index 244461ae54..0fec57755c 100644 --- a/guice/src/test/java/cucumber/runtime/java/guice/collection/CollectionUtil.java +++ b/guice/src/test/java/io/cucumber/guice/collection/CollectionUtil.java @@ -1,4 +1,4 @@ -package cucumber.runtime.java.guice.collection; +package io.cucumber.guice.collection; import java.util.List; diff --git a/guice/src/test/java/cucumber/runtime/java/guice/collection/CollectionUtilTest.java b/guice/src/test/java/io/cucumber/guice/collection/CollectionUtilTest.java similarity index 61% rename from guice/src/test/java/cucumber/runtime/java/guice/collection/CollectionUtilTest.java rename to guice/src/test/java/io/cucumber/guice/collection/CollectionUtilTest.java index f12aef2074..647e9356b7 100644 --- a/guice/src/test/java/cucumber/runtime/java/guice/collection/CollectionUtilTest.java +++ b/guice/src/test/java/io/cucumber/guice/collection/CollectionUtilTest.java @@ -1,13 +1,16 @@ -package cucumber.runtime.java.guice.collection; +package io.cucumber.guice.collection; import org.junit.Before; import org.junit.Test; +import org.junit.jupiter.api.function.Executable; import java.util.ArrayList; import java.util.List; -import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; public class CollectionUtilTest { @@ -18,14 +21,18 @@ public void setUp() { list = new ArrayList(); } - @Test(expected = NullPointerException.class) + @Test public void testNullPointerExceptionIsThrownWhenListIsNull() { - CollectionUtil.removeAllExceptFirstElement(null); + final Executable testMethod = () -> CollectionUtil.removeAllExceptFirstElement(null); + final NullPointerException expectedThrown = assertThrows(NullPointerException.class, testMethod); + assertThat(expectedThrown.getMessage(), is(equalTo("List must not be null."))); } - @Test(expected = IllegalArgumentException.class) + @Test public void testIllegalArgumentExceptionIsThrownWhenListIsEmpty() { - CollectionUtil.removeAllExceptFirstElement(list); + final Executable testMethod = () -> CollectionUtil.removeAllExceptFirstElement(list); + final IllegalArgumentException expectedThrown = assertThrows(IllegalArgumentException.class, testMethod); + assertThat(expectedThrown.getMessage(), is(equalTo("List must contain at least one element."))); } @Test @@ -56,4 +63,5 @@ private void assertThatListContainsOneElement(String element) { assertThat(list.size(), equalTo(1)); assertThat(list.get(0), equalTo(element)); } + } diff --git a/guice/src/test/resources/cucumber/runtime/java/guice/impl/LivesInChildClassLoader.java.txt b/guice/src/test/java/io/cucumber/guice/impl/LivesInChildClassLoader.java.txt similarity index 64% rename from guice/src/test/resources/cucumber/runtime/java/guice/impl/LivesInChildClassLoader.java.txt rename to guice/src/test/java/io/cucumber/guice/impl/LivesInChildClassLoader.java.txt index 8a789bf33b..29445c721c 100644 --- a/guice/src/test/resources/cucumber/runtime/java/guice/impl/LivesInChildClassLoader.java.txt +++ b/guice/src/test/java/io/cucumber/guice/impl/LivesInChildClassLoader.java.txt @@ -1,7 +1,7 @@ -package cucumber.runtime.java.guice.impl; +package io.cucumber.guice.impl; import com.google.inject.Injector; -import cucumber.runtime.java.guice.InjectorSource; +import io.cucumber.guice.InjectorSource; public class LivesInChildClassLoader implements InjectorSource { @Override diff --git a/guice/src/test/java/cucumber/runtime/java/guice/integration/HelloWorldSteps.java b/guice/src/test/java/io/cucumber/guice/integration/HelloWorldSteps.java similarity index 54% rename from guice/src/test/java/cucumber/runtime/java/guice/integration/HelloWorldSteps.java rename to guice/src/test/java/io/cucumber/guice/integration/HelloWorldSteps.java index 14fbbb3a5d..9f4c479408 100644 --- a/guice/src/test/java/cucumber/runtime/java/guice/integration/HelloWorldSteps.java +++ b/guice/src/test/java/io/cucumber/guice/integration/HelloWorldSteps.java @@ -1,7 +1,7 @@ -package cucumber.runtime.java.guice.integration; +package io.cucumber.guice.integration; -import cucumber.api.java.en.Given; -import cucumber.runtime.java.guice.ScenarioScoped; +import io.cucumber.java.en.Given; +import io.cucumber.guice.ScenarioScoped; @ScenarioScoped public class HelloWorldSteps { diff --git a/guice/src/test/java/cucumber/runtime/java/guice/integration/RunCucumberTest.java b/guice/src/test/java/io/cucumber/guice/integration/RunCucumberTest.java similarity index 90% rename from guice/src/test/java/cucumber/runtime/java/guice/integration/RunCucumberTest.java rename to guice/src/test/java/io/cucumber/guice/integration/RunCucumberTest.java index b82b8d8087..e2e32165e9 100644 --- a/guice/src/test/java/cucumber/runtime/java/guice/integration/RunCucumberTest.java +++ b/guice/src/test/java/io/cucumber/guice/integration/RunCucumberTest.java @@ -1,4 +1,4 @@ -package cucumber.runtime.java.guice.integration; +package io.cucumber.guice.integration; import io.cucumber.junit.Cucumber; import org.junit.runner.RunWith; diff --git a/guice/src/test/java/io/cucumber/guice/integration/ScenarioScopedObject.java b/guice/src/test/java/io/cucumber/guice/integration/ScenarioScopedObject.java new file mode 100644 index 0000000000..ef9723e8f8 --- /dev/null +++ b/guice/src/test/java/io/cucumber/guice/integration/ScenarioScopedObject.java @@ -0,0 +1,4 @@ +package io.cucumber.guice.integration; + +public class ScenarioScopedObject { +} diff --git a/guice/src/test/java/cucumber/runtime/java/guice/integration/ScenarioScopedSteps.java b/guice/src/test/java/io/cucumber/guice/integration/ScenarioScopedSteps.java similarity index 81% rename from guice/src/test/java/cucumber/runtime/java/guice/integration/ScenarioScopedSteps.java rename to guice/src/test/java/io/cucumber/guice/integration/ScenarioScopedSteps.java index 93dba7fe0a..39859041dd 100644 --- a/guice/src/test/java/cucumber/runtime/java/guice/integration/ScenarioScopedSteps.java +++ b/guice/src/test/java/io/cucumber/guice/integration/ScenarioScopedSteps.java @@ -1,18 +1,18 @@ -package cucumber.runtime.java.guice.integration; +package io.cucumber.guice.integration; -import cucumber.api.java.en.Given; -import cucumber.api.java.en.Then; -import cucumber.api.java.en.When; -import cucumber.runtime.java.guice.ScenarioScoped; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +import io.cucumber.guice.ScenarioScoped; import javax.inject.Inject; import javax.inject.Provider; import java.util.ArrayList; import java.util.List; -import static cucumber.runtime.java.guice.collection.CollectionUtil.removeAllExceptFirstElement; -import static cucumber.runtime.java.guice.matcher.ElementsAreAllEqualMatcher.elementsAreAllEqual; -import static cucumber.runtime.java.guice.matcher.ElementsAreAllUniqueMatcher.elementsAreAllUnique; +import static io.cucumber.guice.collection.CollectionUtil.removeAllExceptFirstElement; +import static io.cucumber.guice.matcher.ElementsAreAllEqualMatcher.elementsAreAllEqual; +import static io.cucumber.guice.matcher.ElementsAreAllUniqueMatcher.elementsAreAllUnique; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.assertThat; diff --git a/guice/src/test/java/io/cucumber/guice/integration/SingletonObject.java b/guice/src/test/java/io/cucumber/guice/integration/SingletonObject.java new file mode 100644 index 0000000000..1b4326ae95 --- /dev/null +++ b/guice/src/test/java/io/cucumber/guice/integration/SingletonObject.java @@ -0,0 +1,4 @@ +package io.cucumber.guice.integration; + +public class SingletonObject { +} diff --git a/guice/src/test/java/cucumber/runtime/java/guice/integration/SingletonScopedSteps.java b/guice/src/test/java/io/cucumber/guice/integration/SingletonScopedSteps.java similarity index 84% rename from guice/src/test/java/cucumber/runtime/java/guice/integration/SingletonScopedSteps.java rename to guice/src/test/java/io/cucumber/guice/integration/SingletonScopedSteps.java index ad4c324fef..612f0ed51c 100644 --- a/guice/src/test/java/cucumber/runtime/java/guice/integration/SingletonScopedSteps.java +++ b/guice/src/test/java/io/cucumber/guice/integration/SingletonScopedSteps.java @@ -1,17 +1,17 @@ -package cucumber.runtime.java.guice.integration; +package io.cucumber.guice.integration; -import cucumber.api.java.en.Given; -import cucumber.api.java.en.Then; -import cucumber.api.java.en.When; -import cucumber.runtime.java.guice.ScenarioScoped; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +import io.cucumber.guice.ScenarioScoped; import javax.inject.Inject; import javax.inject.Provider; import java.util.ArrayList; import java.util.List; -import static cucumber.runtime.java.guice.collection.CollectionUtil.removeAllExceptFirstElement; -import static cucumber.runtime.java.guice.matcher.ElementsAreAllEqualMatcher.elementsAreAllEqual; +import static io.cucumber.guice.collection.CollectionUtil.removeAllExceptFirstElement; +import static io.cucumber.guice.matcher.ElementsAreAllEqualMatcher.elementsAreAllEqual; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.assertThat; diff --git a/guice/src/test/java/io/cucumber/guice/integration/UnScopedObject.java b/guice/src/test/java/io/cucumber/guice/integration/UnScopedObject.java new file mode 100644 index 0000000000..992f39e63e --- /dev/null +++ b/guice/src/test/java/io/cucumber/guice/integration/UnScopedObject.java @@ -0,0 +1,4 @@ +package io.cucumber.guice.integration; + +public class UnScopedObject { +} diff --git a/guice/src/test/java/cucumber/runtime/java/guice/integration/UnScopedSteps.java b/guice/src/test/java/io/cucumber/guice/integration/UnScopedSteps.java similarity index 84% rename from guice/src/test/java/cucumber/runtime/java/guice/integration/UnScopedSteps.java rename to guice/src/test/java/io/cucumber/guice/integration/UnScopedSteps.java index 1909f4855e..3405e2af1d 100644 --- a/guice/src/test/java/cucumber/runtime/java/guice/integration/UnScopedSteps.java +++ b/guice/src/test/java/io/cucumber/guice/integration/UnScopedSteps.java @@ -1,15 +1,15 @@ -package cucumber.runtime.java.guice.integration; +package io.cucumber.guice.integration; -import cucumber.api.java.en.Given; -import cucumber.api.java.en.Then; -import cucumber.api.java.en.When; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; import javax.inject.Inject; import javax.inject.Provider; import java.util.ArrayList; import java.util.List; -import static cucumber.runtime.java.guice.matcher.ElementsAreAllUniqueMatcher.elementsAreAllUnique; +import static io.cucumber.guice.matcher.ElementsAreAllUniqueMatcher.elementsAreAllUnique; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.assertThat; diff --git a/guice/src/test/java/cucumber/runtime/java/guice/integration/UnusedGlue.java b/guice/src/test/java/io/cucumber/guice/integration/UnusedGlue.java similarity index 72% rename from guice/src/test/java/cucumber/runtime/java/guice/integration/UnusedGlue.java rename to guice/src/test/java/io/cucumber/guice/integration/UnusedGlue.java index 2847c71ec5..8370bdeb79 100644 --- a/guice/src/test/java/cucumber/runtime/java/guice/integration/UnusedGlue.java +++ b/guice/src/test/java/io/cucumber/guice/integration/UnusedGlue.java @@ -1,7 +1,7 @@ -package cucumber.runtime.java.guice.integration; +package io.cucumber.guice.integration; -import cucumber.api.java.Before; -import cucumber.api.java.en.Given; +import io.cucumber.java.Before; +import io.cucumber.java.en.Given; public class UnusedGlue { public UnusedGlue() { diff --git a/guice/src/test/java/cucumber/runtime/java/guice/integration/YourInjectorSource.java b/guice/src/test/java/io/cucumber/guice/integration/YourInjectorSource.java similarity index 58% rename from guice/src/test/java/cucumber/runtime/java/guice/integration/YourInjectorSource.java rename to guice/src/test/java/io/cucumber/guice/integration/YourInjectorSource.java index 39a0e2190f..df4bfa18cc 100644 --- a/guice/src/test/java/cucumber/runtime/java/guice/integration/YourInjectorSource.java +++ b/guice/src/test/java/io/cucumber/guice/integration/YourInjectorSource.java @@ -1,12 +1,10 @@ -package cucumber.runtime.java.guice.integration; +package io.cucumber.guice.integration; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Stage; -import cucumber.api.guice.CucumberModules; -import cucumber.api.guice.CucumberScopes; -import cucumber.runtime.java.guice.InjectorSource; -import cucumber.runtime.java.guice.ScenarioScope; +import io.cucumber.guice.CucumberModules; +import io.cucumber.guice.InjectorSource; public class YourInjectorSource implements InjectorSource { diff --git a/guice/src/test/java/cucumber/runtime/java/guice/integration/YourModule.java b/guice/src/test/java/io/cucumber/guice/integration/YourModule.java similarity index 72% rename from guice/src/test/java/cucumber/runtime/java/guice/integration/YourModule.java rename to guice/src/test/java/io/cucumber/guice/integration/YourModule.java index 8a8b631585..3172662bd4 100644 --- a/guice/src/test/java/cucumber/runtime/java/guice/integration/YourModule.java +++ b/guice/src/test/java/io/cucumber/guice/integration/YourModule.java @@ -1,9 +1,8 @@ -package cucumber.runtime.java.guice.integration; +package io.cucumber.guice.integration; import com.google.inject.AbstractModule; import com.google.inject.Scopes; -import cucumber.api.guice.CucumberScopes; -import cucumber.runtime.java.guice.ScenarioScoped; +import io.cucumber.guice.ScenarioScoped; public class YourModule extends AbstractModule { diff --git a/guice/src/test/resources/cucumber/runtime/java/guice/integration/guice-no-scope.feature b/guice/src/test/java/io/cucumber/guice/integration/guice-no-scope.feature similarity index 100% rename from guice/src/test/resources/cucumber/runtime/java/guice/integration/guice-no-scope.feature rename to guice/src/test/java/io/cucumber/guice/integration/guice-no-scope.feature diff --git a/guice/src/test/resources/cucumber/runtime/java/guice/integration/guice-scenario-scope.feature b/guice/src/test/java/io/cucumber/guice/integration/guice-scenario-scope.feature similarity index 100% rename from guice/src/test/resources/cucumber/runtime/java/guice/integration/guice-scenario-scope.feature rename to guice/src/test/java/io/cucumber/guice/integration/guice-scenario-scope.feature diff --git a/guice/src/test/resources/cucumber/runtime/java/guice/integration/guice-singleton-scope.feature b/guice/src/test/java/io/cucumber/guice/integration/guice-singleton-scope.feature similarity index 100% rename from guice/src/test/resources/cucumber/runtime/java/guice/integration/guice-singleton-scope.feature rename to guice/src/test/java/io/cucumber/guice/integration/guice-singleton-scope.feature diff --git a/guice/src/test/resources/cucumber/runtime/java/guice/integration/hello.feature b/guice/src/test/java/io/cucumber/guice/integration/hello.feature similarity index 100% rename from guice/src/test/resources/cucumber/runtime/java/guice/integration/hello.feature rename to guice/src/test/java/io/cucumber/guice/integration/hello.feature diff --git a/guice/src/test/java/cucumber/runtime/java/guice/matcher/AbstractMatcherTest.java b/guice/src/test/java/io/cucumber/guice/matcher/AbstractMatcherTest.java similarity index 98% rename from guice/src/test/java/cucumber/runtime/java/guice/matcher/AbstractMatcherTest.java rename to guice/src/test/java/io/cucumber/guice/matcher/AbstractMatcherTest.java index 737948f93a..23b827b5b7 100644 --- a/guice/src/test/java/cucumber/runtime/java/guice/matcher/AbstractMatcherTest.java +++ b/guice/src/test/java/io/cucumber/guice/matcher/AbstractMatcherTest.java @@ -1,4 +1,4 @@ -package cucumber.runtime.java.guice.matcher; +package io.cucumber.guice.matcher; import junit.framework.TestCase; import org.hamcrest.Description; diff --git a/guice/src/test/java/cucumber/runtime/java/guice/matcher/ElementsAreAllEqualMatcher.java b/guice/src/test/java/io/cucumber/guice/matcher/ElementsAreAllEqualMatcher.java similarity index 97% rename from guice/src/test/java/cucumber/runtime/java/guice/matcher/ElementsAreAllEqualMatcher.java rename to guice/src/test/java/io/cucumber/guice/matcher/ElementsAreAllEqualMatcher.java index 6cf8cf4f48..cb69d651a4 100644 --- a/guice/src/test/java/cucumber/runtime/java/guice/matcher/ElementsAreAllEqualMatcher.java +++ b/guice/src/test/java/io/cucumber/guice/matcher/ElementsAreAllEqualMatcher.java @@ -1,4 +1,4 @@ -package cucumber.runtime.java.guice.matcher; +package io.cucumber.guice.matcher; import org.hamcrest.Description; import org.hamcrest.Factory; diff --git a/guice/src/test/java/cucumber/runtime/java/guice/matcher/ElementsAreAllEqualMatcherTest.java b/guice/src/test/java/io/cucumber/guice/matcher/ElementsAreAllEqualMatcherTest.java similarity index 91% rename from guice/src/test/java/cucumber/runtime/java/guice/matcher/ElementsAreAllEqualMatcherTest.java rename to guice/src/test/java/io/cucumber/guice/matcher/ElementsAreAllEqualMatcherTest.java index 1dca321b9b..a5134f02e9 100644 --- a/guice/src/test/java/cucumber/runtime/java/guice/matcher/ElementsAreAllEqualMatcherTest.java +++ b/guice/src/test/java/io/cucumber/guice/matcher/ElementsAreAllEqualMatcherTest.java @@ -1,15 +1,13 @@ -package cucumber.runtime.java.guice.matcher; +package io.cucumber.guice.matcher; import org.hamcrest.Matcher; import java.util.Arrays; import java.util.Collection; -import static cucumber.runtime.java.guice.matcher.ElementsAreAllEqualMatcher.elementsAreAllEqual; - public class ElementsAreAllEqualMatcherTest extends AbstractMatcherTest { - private final Matcher> matcher = elementsAreAllEqual(); + private final Matcher> matcher = ElementsAreAllEqualMatcher.elementsAreAllEqual(); @Override protected Matcher createMatcher() { diff --git a/guice/src/test/java/cucumber/runtime/java/guice/matcher/ElementsAreAllMatcher.java b/guice/src/test/java/io/cucumber/guice/matcher/ElementsAreAllMatcher.java similarity index 96% rename from guice/src/test/java/cucumber/runtime/java/guice/matcher/ElementsAreAllMatcher.java rename to guice/src/test/java/io/cucumber/guice/matcher/ElementsAreAllMatcher.java index 5e2a3b2e75..f0bd803fc0 100644 --- a/guice/src/test/java/cucumber/runtime/java/guice/matcher/ElementsAreAllMatcher.java +++ b/guice/src/test/java/io/cucumber/guice/matcher/ElementsAreAllMatcher.java @@ -1,4 +1,4 @@ -package cucumber.runtime.java.guice.matcher; +package io.cucumber.guice.matcher; import org.hamcrest.Description; import org.hamcrest.TypeSafeDiagnosingMatcher; diff --git a/guice/src/test/java/cucumber/runtime/java/guice/matcher/ElementsAreAllUniqueMatcher.java b/guice/src/test/java/io/cucumber/guice/matcher/ElementsAreAllUniqueMatcher.java similarity index 97% rename from guice/src/test/java/cucumber/runtime/java/guice/matcher/ElementsAreAllUniqueMatcher.java rename to guice/src/test/java/io/cucumber/guice/matcher/ElementsAreAllUniqueMatcher.java index e9435cd245..9966139e9e 100644 --- a/guice/src/test/java/cucumber/runtime/java/guice/matcher/ElementsAreAllUniqueMatcher.java +++ b/guice/src/test/java/io/cucumber/guice/matcher/ElementsAreAllUniqueMatcher.java @@ -1,4 +1,4 @@ -package cucumber.runtime.java.guice.matcher; +package io.cucumber.guice.matcher; import org.hamcrest.Description; import org.hamcrest.Factory; diff --git a/guice/src/test/java/cucumber/runtime/java/guice/matcher/ElementsAreAllUniqueMatcherTest.java b/guice/src/test/java/io/cucumber/guice/matcher/ElementsAreAllUniqueMatcherTest.java similarity index 91% rename from guice/src/test/java/cucumber/runtime/java/guice/matcher/ElementsAreAllUniqueMatcherTest.java rename to guice/src/test/java/io/cucumber/guice/matcher/ElementsAreAllUniqueMatcherTest.java index ccbf9f557b..2525f4142c 100644 --- a/guice/src/test/java/cucumber/runtime/java/guice/matcher/ElementsAreAllUniqueMatcherTest.java +++ b/guice/src/test/java/io/cucumber/guice/matcher/ElementsAreAllUniqueMatcherTest.java @@ -1,15 +1,13 @@ -package cucumber.runtime.java.guice.matcher; +package io.cucumber.guice.matcher; import org.hamcrest.Matcher; import java.util.Arrays; import java.util.Collection; -import static cucumber.runtime.java.guice.matcher.ElementsAreAllUniqueMatcher.elementsAreAllUnique; - public class ElementsAreAllUniqueMatcherTest extends AbstractMatcherTest { - private final Matcher> matcher = elementsAreAllUnique(); + private final Matcher> matcher = ElementsAreAllUniqueMatcher.elementsAreAllUnique(); @Override protected Matcher createMatcher() { diff --git a/guice/src/test/resources/cucumber.properties b/guice/src/test/resources/cucumber.properties index 5fc09fea75..b3672ffdfa 100644 --- a/guice/src/test/resources/cucumber.properties +++ b/guice/src/test/resources/cucumber.properties @@ -1 +1 @@ -guice.injector-source=cucumber.runtime.java.guice.integration.YourInjectorSource +guice.injector-source=io.cucumber.guice.integration.YourInjectorSource diff --git a/guice/src/test/resources/cucumber/runtime/java/guice/impl/LivesInChildClassLoader.class.bin b/guice/src/test/resources/cucumber/runtime/java/guice/impl/LivesInChildClassLoader.class.bin deleted file mode 100644 index da2c952a67..0000000000 Binary files a/guice/src/test/resources/cucumber/runtime/java/guice/impl/LivesInChildClassLoader.class.bin and /dev/null differ diff --git a/guice/src/test/resources/io/cucumber/guice/impl/LivesInChildClassLoader.class.bin b/guice/src/test/resources/io/cucumber/guice/impl/LivesInChildClassLoader.class.bin new file mode 100644 index 0000000000..8c0953fdcf Binary files /dev/null and b/guice/src/test/resources/io/cucumber/guice/impl/LivesInChildClassLoader.class.bin differ diff --git a/java/README.md b/java/README.md new file mode 100644 index 0000000000..c2f86a8d75 --- /dev/null +++ b/java/README.md @@ -0,0 +1,163 @@ +Cucumber Java +============= + +Provides annotation based step definitions. To use add the `cucumber-java` dependency to your pom.xml: + +```xml + + [...] + + io.cucumber + cucumber-java + ${cucumber.version} + test + + [...] + +``` + +## Step Definitions + +Declare a step definition by annotating a method. It is possible use the same method for multiple steps by repeating +the annotation. For localized annotations import the annotations from `io.cucumber.java..*` + +```java +package com.example.app; + +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; + +import static org.junit.Assert.assertEquals; + +public class CalculatorSteps{ + private RpnCalculator calc; + + @Given("a calculator I just turned on") + public void a_calculator_I_just_turned_on() { + calc = new RpnCalculator(); + } + + @When("I add {int} and {int}") + public void adding(int arg1, int arg2) { + calc.push(arg1); + calc.push(arg2); + calc.push("+"); + } + + @Then("the result is {int}") + public void the_result_is(double expected) { + assertEquals(expected, calc.value()); + } +} +``` + +## Hooks + +Declare hooks that will be executed before/after each scenario/step by annotating a method. The method may declare an +argument of type `io.cucumber.core.api.Scenario`. + + * `@Before` + * `@After` + * `@BeforeStep` + * `@AfterStep` + +## Transformers + +### Parameter Type + +Step definition parameter types can be declared by using `@ParameterType`. The name of the annotated method will be used +as the parameter name. + +```java +package com.example.app; + +import io.cucumber.java.ParameterType; +import io.cucumber.java.en.Given; + +import java.time.LocalDate; + +public class Steps { + + @ParameterType("([0-9]{4})-([0-9]{2})-([0-9]{2})") + public LocalDate iso8601Date(String year, String month, String day) { + return LocalDate.of(Integer.parseInt(year), Integer.parseInt(month), Integer.parseInt(day)); + } + + @Given("today is {iso8601Date}") + public void today_is(LocalDate date) { + + } +} +``` + +### Data Table Type + +Data table types can be declared by annotating a method with `@DataTableType`. Depending on the parameter type this +will be either a: + * `String` -> `io.cucumber.datatable.TableCellTranformer` + * `Map` -> `io.cucumber.datatable.TableEntry` + * `List `io.cucumber.datatable.TableRow` + * `DataTable` -> `io.cucumber.datatable.TableTransformer` + +```java +package com.example.app; + +import io.cucumber.datatable.DataTable; +import io.cucumber.java.DataTableType; + +import java.util.List; +import java.util.Map; + +public class Steps { + + @DataTableType + public Author authorEntryTransformer(Map entry) { + return new Author( + entry.get("firstName"), + entry.get("lastName"), + entry.get("birthDate")); + } + + @DataTableType + public Author authorEntryTransformer(List row) { + return new Author( + row.get(0), + row.get(0), + row.get(0)); + } +} + +``` + +### Default Transformers + +Default transformers allow you to specific a transformer that will be used when there is no transform defined. This can +be combined with an object mapper like Jackson to quickly transform well known string representations to Java objects. + + * `@DefaultParameterTransformer` + * `@DefaultDataTableEntryTransformer` + * `@DefaultDataTableCellTransformer` + + ```java +package com.example.app; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.cucumber.java.DefaultDataTableCellTransformer; +import io.cucumber.java.DefaultDataTableEntryTransformer; +import io.cucumber.java.DefaultParameterTransformer; + +import java.lang.reflect.Type; + +public class DataTableSteps { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @DefaultParameterTransformer + @DefaultDataTableEntryTransformer + @DefaultDataTableCellTransformer + public Object defaultTransformer(Object fromValue, Type toValueType) { + return objectMapper.convertValue(fromValue, objectMapper.constructType(toValueType)); + } +} +``` \ No newline at end of file diff --git a/java/pom.xml b/java/pom.xml index 528a75bf77..4c3c6cde8d 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 4.7.2-SNAPSHOT + 5.0.0-SNAPSHOT cucumber-java @@ -25,11 +25,25 @@ cucumber-junit test + + + org.hamcrest + hamcrest-library + test + + - junit - junit + org.junit.jupiter + junit-jupiter-api test + + + org.junit.vintage + junit-vintage-engine + test + + org.mockito mockito-core diff --git a/java/src/main/groovy/annotation.java.gsp b/java/src/main/groovy/annotation.java.gsp index 30476faf58..f1ff53cd21 100644 --- a/java/src/main/groovy/annotation.java.gsp +++ b/java/src/main/groovy/annotation.java.gsp @@ -1,11 +1,13 @@ package io.cucumber.java.${lang}; -import cucumber.runtime.java.StepDefAnnotation; +import io.cucumber.java.StepDefinitionAnnotations; +import io.cucumber.java.StepDefinitionAnnotation; import org.apiguardian.api.API; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -29,8 +31,9 @@ import java.lang.annotation.Target; */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) -@StepDefAnnotation +@StepDefinitionAnnotation @Documented +@Repeatable(${kw}.${kw}s.class) @API(status = API.Status.STABLE) public @interface ${kw} { /** @@ -49,6 +52,20 @@ public @interface ${kw} { * cucumber will wait for the this hook to finish. * * @return timeout in milliseconds. 0 (default) means no restriction. + * @deprecated use a library based solution instead. E.g. Awaitility + * or JUnit 5s Assertions.assertTimeout. */ + @Deprecated long timeout() default 0; + + /** + * Allows the use of multiple '${kw}'s on a single method. + */ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @StepDefinitionAnnotations + @Documented + @interface ${kw}s { + ${kw}[] value(); + } } diff --git a/java/src/main/groovy/deprecated-annotation.java.gsp b/java/src/main/groovy/deprecated-annotation.java.gsp deleted file mode 100644 index 9f6ced552f..0000000000 --- a/java/src/main/groovy/deprecated-annotation.java.gsp +++ /dev/null @@ -1,54 +0,0 @@ -package cucumber.api.java.${lang}; - -import cucumber.runtime.java.StepDefAnnotation; - -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; - -/** - * 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. - * - * @deprecated use {@link io.cucumber.java.${lang}.${kw}} instead. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -@StepDefAnnotation -@Documented -@Deprecated -public @interface ${kw} { - /** - * A cucumber or regular expression. - * - * @return a cucumber or regular expression - */ - String value(); - - /** - * Duration in milliseconds this step is allowed to run. Cucumber - * will mark the step as failed when exceeded. - * - * When the maximum duration is exceeded the thread will - * receive an in interrupt. Note: if the interrupt is ignored - * cucumber will wait for the this hook to finish. - * - * @return timeout in milliseconds. 0 (default) means no restriction. - */ - long timeout() default 0; -} diff --git a/java/src/main/groovy/generate-annotations.groovy b/java/src/main/groovy/generate-annotations.groovy index cabef31935..0d716d1dfe 100644 --- a/java/src/main/groovy/generate-annotations.groovy +++ b/java/src/main/groovy/generate-annotations.groovy @@ -5,7 +5,6 @@ import java.text.Normalizer SimpleTemplateEngine engine = new SimpleTemplateEngine() def templateSource = new File(project.baseDir, "src/main/groovy/annotation.java.gsp").getText() -def deprecatedTemplateSource = new File(project.baseDir, "src/main/groovy/deprecated-annotation.java.gsp").getText() static def normalize(s) { if (System.getProperty("java.version").startsWith("1.6")) { @@ -32,13 +31,6 @@ def package_info_java = """\ package io.cucumber.java.\${normalized_language}; """ -def deprecated_package_info_java = """\ -/** - * \${locale.getDisplayLanguage()} - */ -package cucumber.api.java.\${normalized_language}; -""" - def unsupported = ["em"] // The generated files for Emoij do not compile. def dialectProvider = new GherkinDialectProvider() @@ -54,31 +46,17 @@ GherkinDialectProvider.DIALECTS.keySet().each { language -> def file = new File(project.baseDir, "target/generated-sources/i18n/java/io/cucumber/java/${normalized_language}/${normalized_kw}.java") if (!file.exists()) { // Haitian has two translations that only differ by case - Sipozeke and SipozeKe - // Some file systems are unable to distinguish between them and overwrite the other one :-( + // Some file systems are unable to distiguish between them and overwrite the other one :-( file.parentFile.mkdirs() file.write(template.toString(), "UTF-8") } - - def deprecatedTemplate = engine.createTemplate(deprecatedTemplateSource).make(binding) - def deprecatedFile = new File(project.baseDir, "target/generated-sources/i18n/java/cucumber/api/java/${normalized_language}/${normalized_kw}.java") - if (!deprecatedFile.exists()) { - // Haitian has two translations that only differ by case - Sipozeke and SipozeKe - // Some file systems are unable to distinguish between them and overwrite the other one :-( - deprecatedFile.parentFile.mkdirs() - deprecatedFile.write(deprecatedTemplate.toString(), "UTF-8") - } } // package-info.java def locale = localeFor(dialect.language) def binding = [ "locale": locale, "normalized_language": normalized_language ] - def html = engine.createTemplate(package_info_java).make(binding).toString() def file = new File(project.baseDir, "target/generated-sources/i18n/java/io/cucumber/java/${normalized_language}/package-info.java") file.write(html, "UTF-8") - - def deprecatedHtml = engine.createTemplate(deprecated_package_info_java).make(binding).toString() - def deprecatedFile = new File(project.baseDir, "target/generated-sources/i18n/java/cucumber/api/java/${normalized_language}/package-info.java") - deprecatedFile.write(deprecatedHtml, "UTF-8") } } \ No newline at end of file diff --git a/java/src/main/java/cucumber/api/java/After.java b/java/src/main/java/cucumber/api/java/After.java deleted file mode 100644 index a07f1f5488..0000000000 --- a/java/src/main/java/cucumber/api/java/After.java +++ /dev/null @@ -1,30 +0,0 @@ -package cucumber.api.java; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * @deprecated use {@link io.cucumber.java.After} instead. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -@Deprecated -public @interface After { - /** - * @return a tag expression - */ - String[] value() default {}; - - /** - * @return max amount of milliseconds this is allowed to run for. 0 (default) means no restriction. - */ - long timeout() default 0; - - /** - * @return the order in which this hook should run. Higher numbers are run first. - * The default order is 10000. - */ - int order() default 10000; -} diff --git a/java/src/main/java/cucumber/api/java/AfterStep.java b/java/src/main/java/cucumber/api/java/AfterStep.java deleted file mode 100644 index 71a0b72607..0000000000 --- a/java/src/main/java/cucumber/api/java/AfterStep.java +++ /dev/null @@ -1,30 +0,0 @@ -package cucumber.api.java; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * @deprecated use {@link io.cucumber.java.AfterStep} instead. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -@Deprecated -public @interface AfterStep { - /** - * @return a tag expression - */ - String[] value() default {}; - - /** - * @return max amount of milliseconds this is allowed to run for. 0 (default) means no restriction. - */ - long timeout() default 0; - - /** - * @return the order in which this hook should run. Higher numbers are run first. - * The default order is 10000. - */ - int order() default 10000; -} diff --git a/java/src/main/java/cucumber/api/java/Before.java b/java/src/main/java/cucumber/api/java/Before.java deleted file mode 100644 index 2ab2b952fc..0000000000 --- a/java/src/main/java/cucumber/api/java/Before.java +++ /dev/null @@ -1,30 +0,0 @@ -package cucumber.api.java; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * @deprecated use {@link io.cucumber.java.Before} instead. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -@Deprecated -public @interface Before { - /** - * @return a tag expression - */ - String[] value() default {}; - - /** - * @return max amount of milliseconds this is allowed to run for. 0 (default) means no restriction. - */ - long timeout() default 0; - - /** - * @return the order in which this hook should run. Lower numbers are run first. - * The default order is 10000. - */ - int order() default 10000; -} diff --git a/java/src/main/java/cucumber/api/java/BeforeStep.java b/java/src/main/java/cucumber/api/java/BeforeStep.java deleted file mode 100644 index cf14dfb584..0000000000 --- a/java/src/main/java/cucumber/api/java/BeforeStep.java +++ /dev/null @@ -1,30 +0,0 @@ -package cucumber.api.java; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * @deprecated use {@link io.cucumber.java.BeforeStep} instead. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -@Deprecated -public @interface BeforeStep { - /** - * @return a tag expression - */ - String[] value() default {}; - - /** - * @return max amount of milliseconds this is allowed to run for. 0 (default) means no restriction. - */ - long timeout() default 0; - - /** - * @return the order in which this hook should run. Lower numbers are run first. - * The default order is 10000. - */ - int order() default 10000; -} diff --git a/java/src/main/java/cucumber/api/java/ObjectFactory.java b/java/src/main/java/cucumber/api/java/ObjectFactory.java deleted file mode 100644 index 0b70120ea8..0000000000 --- a/java/src/main/java/cucumber/api/java/ObjectFactory.java +++ /dev/null @@ -1,38 +0,0 @@ -package cucumber.api.java; - -/** - * Minimal facade for Dependency Injection containers - * - * @deprecated use @code io.cucumber.core.backend.ObjectFactory instead - */ -@Deprecated -public interface ObjectFactory { - - /** - * Instantiate glue code before scenario execution. Called once per scenario. - */ - void start(); - - /** - * Dispose glue code after scenario execution. Called once per scenario. - */ - void stop(); - - /** - * Collects glue classes in the classpath. Called once on init. - * - * @param glueClass Glue class containing cucumber.api annotations (Before, Given, When, ...) - * @return true if stepdefs and hooks in this class should be used, false if they should be ignored. - */ - boolean addClass(Class glueClass); - - /** - * Provides the glue instances used to execute the current scenario. The instance can be prepared in - * {@link #start()}. - * - * @param glueClass type of instance to be created. - * @param type of Glue class - * @return new Glue instance of type T - */ - T getInstance(Class glueClass); -} diff --git a/java/src/main/java/cucumber/api/java8/GlueBase.java b/java/src/main/java/cucumber/api/java8/GlueBase.java deleted file mode 100644 index 7e6e421402..0000000000 --- a/java/src/main/java/cucumber/api/java8/GlueBase.java +++ /dev/null @@ -1,4 +0,0 @@ -package cucumber.api.java8; - -public interface GlueBase { -} diff --git a/java/src/main/java/cucumber/runtime/java/DefaultJavaObjectFactory.java b/java/src/main/java/cucumber/runtime/java/DefaultJavaObjectFactory.java deleted file mode 100644 index 0669963cd4..0000000000 --- a/java/src/main/java/cucumber/runtime/java/DefaultJavaObjectFactory.java +++ /dev/null @@ -1,49 +0,0 @@ -package cucumber.runtime.java; - -import cucumber.api.java.ObjectFactory; -import cucumber.runtime.CucumberException; - -import java.lang.reflect.Constructor; -import java.util.HashMap; -import java.util.Map; - -/** - * This class has package scope so it doesn't get loaded by reflection, - * thereby colliding with other DI implementations. - */ -class DefaultJavaObjectFactory implements ObjectFactory { - private final Map, Object> instances = new HashMap, Object>(); - - public void start() { - // No-op - } - - public void stop() { - instances.clear(); - } - - public boolean addClass(Class clazz) { - return true; - } - - public T getInstance(Class type) { - T instance = type.cast(instances.get(type)); - if (instance == null) { - instance = cacheNewInstance(type); - } - return instance; - } - - private T cacheNewInstance(Class type) { - try { - Constructor constructor = type.getConstructor(); - T instance = constructor.newInstance(); - instances.put(type, instance); - return instance; - } catch (NoSuchMethodException e) { - throw new CucumberException(String.format("%s doesn't have an empty constructor. If you need DI, put cucumber-picocontainer on the classpath", type), e); - } catch (Exception e) { - throw new CucumberException(String.format("Failed to instantiate %s", type), e); - } - } -} diff --git a/java/src/main/java/cucumber/runtime/java/Function.java b/java/src/main/java/cucumber/runtime/java/Function.java deleted file mode 100644 index fe6ea885e2..0000000000 --- a/java/src/main/java/cucumber/runtime/java/Function.java +++ /dev/null @@ -1,7 +0,0 @@ -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 deleted file mode 100644 index 76f9d1e66e..0000000000 --- a/java/src/main/java/cucumber/runtime/java/Java8Snippet.java +++ /dev/null @@ -1,13 +0,0 @@ -package cucumber.runtime.java; - -final class Java8Snippet extends AbstractJavaSnippet { - - @Override - public String template() { - return "" + - "{0}(\"{1}\", ({3}) -> '{'\n" + - " // {4}\n" + - "{5} throw new cucumber.api.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 deleted file mode 100644 index a66299b1d8..0000000000 --- a/java/src/main/java/cucumber/runtime/java/JavaBackend.java +++ /dev/null @@ -1,268 +0,0 @@ -package cucumber.runtime.java; - -import cucumber.api.java.After; -import cucumber.api.java.AfterStep; -import cucumber.api.java.Before; -import cucumber.api.java.BeforeStep; -import cucumber.api.java.ObjectFactory; -import cucumber.api.java8.GlueBase; -import cucumber.runtime.Backend; -import cucumber.runtime.ClassFinder; -import cucumber.runtime.CucumberException; -import cucumber.runtime.Env; -import cucumber.runtime.Glue; -import cucumber.runtime.HookDefinition; -import cucumber.runtime.StepDefinition; -import cucumber.runtime.Utils; -import cucumber.runtime.io.ResourceLoader; -import cucumber.runtime.io.ResourceLoaderClassFinder; -import cucumber.runtime.snippets.FunctionNameGenerator; -import cucumber.runtime.snippets.Snippet; -import cucumber.runtime.snippets.SnippetGenerator; -import gherkin.pickles.PickleStep; -import io.cucumber.stepexpression.TypeRegistry; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.net.URI; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import static cucumber.runtime.java.ObjectFactoryLoader.loadObjectFactory; -import static java.lang.Thread.currentThread; - -public class JavaBackend implements Backend, LambdaGlueRegistry { - - static final String OBJECT_FACTORY_KEY = "cucumber.object-factory"; - - /** - * Returns a specified class name for an {@link io.cucumber.core.backend.ObjectFactory} or null. - * An ObjectFactory might be specified in the options using the cucumber.object-factory key. - */ - static String getObjectFactoryClassName(Env env) - { - return env.get(OBJECT_FACTORY_KEY); - } - - /** - * Returns a specified class name for an {@link cucumber.api.java.ObjectFactory} or null. - * An ObjectFactory might be specified in the options using the classname of {@link cucumber.api.java.ObjectFactory} as key. - */ - @Deprecated - static String getDeprecatedObjectFactoryClassName(Env env) - { - return env.get(ObjectFactory.class.getName()); - } - - - private final SnippetGenerator snippetGenerator; - private final TypeRegistry typeRegistry; - - private Snippet createSnippet() { - ClassLoader classLoader = currentThread().getContextClassLoader(); - try { - classLoader.loadClass("cucumber.runtime.java8.LambdaGlueBase"); - return new Java8Snippet(); - } catch (ClassNotFoundException thatsOk) { - return new JavaSnippet(); - } - } - - private final ObjectFactory objectFactory; - private final ClassFinder classFinder; - - private final MethodScanner methodScanner; - private Glue glue; - private List> glueBaseClasses = new ArrayList>(); - - /** - * The constructor called by reflection by default. - * - * @param resourceLoader - */ - public JavaBackend(ResourceLoader resourceLoader, TypeRegistry typeRegistry) { - this(new ResourceLoaderClassFinder(resourceLoader, currentThread().getContextClassLoader()), typeRegistry); - } - - /** - * This constructor will create an object factory. - * The default object factory will be used unless an object factory of type {@link io.cucumber.core.backend.ObjectFactory} is specified - * by its class name in the cucumber.properties under the JavaBackend.OBJECT_FACTORY_KEY key. - * Alternatively a deprecated object factory of type {@link cucumber.api.java.ObjectFactory} can be specified in cucumber.properties - * under the key ObjectFactory.class.getName(). - */ - JavaBackend(ClassFinder classFinder, TypeRegistry typeRegistry) { - this(loadObjectFactory(classFinder, getObjectFactoryClassName(Env.INSTANCE),getDeprecatedObjectFactoryClassName(Env.INSTANCE)), classFinder, typeRegistry); - } - - 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 - public void loadGlue(Glue glue, List gluePaths) { - this.glue = glue; - // Scan for Java7 style glue (annotated methods) - methodScanner.scan(this, gluePaths); - - // Scan for Java8 style glue (lambdas) - for (final URI gluePath : gluePaths) { - Collection> glueDefinerClasses = classFinder.getDescendants(GlueBase.class, gluePath); - for (final Class glueClass : glueDefinerClasses) { - if (glueClass.isInterface()) { - continue; - } - - if (objectFactory.addClass(glueClass)) { - glueBaseClasses.add(glueClass); - } - } - } - } - - /** - * Convenience method for frameworks that wish to load glue from methods explicitly (possibly - * found with a different mechanism than Cucumber's built-in classpath scanning). - * - * @param glue where stepdefs and hooks will be added. - * @param method a candidate method. - * @param glueCodeClass the class implementing the method. Must not be a subclass of the class implementing the method. - */ - public void loadGlue(Glue glue, Method method, Class glueCodeClass) { - this.glue = glue; - methodScanner.scan(this, method, glueCodeClass); - } - - @Override - public void buildWorld() { - objectFactory.start(); - - // Instantiate all the stepdef classes for java8 - the stepdef will be initialised - // in the constructor. - try { - INSTANCE.set(this); - for (Class glueBaseClass : glueBaseClasses) { - objectFactory.getInstance(glueBaseClass); - } - } finally { - INSTANCE.remove(); - } - } - - @Override - public void disposeWorld() { - objectFactory.stop(); - } - - @Override - public List getSnippet(PickleStep step, String keyword, FunctionNameGenerator functionNameGenerator) { - return snippetGenerator.getSnippet(step, keyword, functionNameGenerator); - } - - void addStepDefinition(Annotation annotation, Method method) { - try { - if (objectFactory.addClass(method.getDeclaringClass())) { - glue.addStepDefinition( - new JavaStepDefinition( - method, - expression(annotation), - timeoutMillis(annotation), - objectFactory, - typeRegistry)); - } - } catch (CucumberException e) { - throw e; - } catch (Throwable e) { - throw new CucumberException(e); - } - } - - @Override - public void addStepDefinition(Function stepDefinitionFunction) { - glue.addStepDefinition(stepDefinitionFunction.apply(typeRegistry)); - } - - void addHook(Annotation annotation, Method method) { - if (objectFactory.addClass(method.getDeclaringClass())) { - if (annotation.annotationType().equals(io.cucumber.java.Before.class)) { - io.cucumber.java.Before before = (io.cucumber.java.Before) annotation; - String[] tagExpressions = new String[]{before.value()}; - long timeout = before.timeout(); - addBeforeHookDefinition(new JavaHookDefinition(method, tagExpressions, before.order(), timeout, objectFactory)); - } else if (annotation.annotationType().equals(io.cucumber.java.After.class)) { - io.cucumber.java.After after = (io.cucumber.java.After) annotation; - String[] tagExpressions = new String[]{after.value()}; - long timeout = after.timeout(); - addAfterHookDefinition(new JavaHookDefinition(method, tagExpressions, after.order(), timeout, objectFactory)); - } else if (annotation.annotationType().equals(io.cucumber.java.BeforeStep.class)) { - io.cucumber.java.BeforeStep beforeStep = (io.cucumber.java.BeforeStep) annotation; - String[] tagExpressions = new String[]{beforeStep.value()}; - long timeout = beforeStep.timeout(); - addBeforeStepHookDefinition(new JavaHookDefinition(method, tagExpressions, beforeStep.order(), timeout, objectFactory)); - } else if (annotation.annotationType().equals(io.cucumber.java.AfterStep.class)) { - io.cucumber.java.AfterStep afterStep = (io.cucumber.java.AfterStep) annotation; - String[] tagExpressions = new String[]{afterStep.value()}; - long timeout = afterStep.timeout(); - addAfterStepHookDefinition(new JavaHookDefinition(method, tagExpressions, afterStep.order(), timeout, objectFactory)); - } else if (annotation.annotationType().equals(Before.class)) { - Before before = (Before) annotation; - String[] tagExpressions = before.value(); - long timeout = before.timeout(); - addBeforeHookDefinition(new JavaHookDefinition(method, tagExpressions, before.order(), timeout, objectFactory)); - } else if (annotation.annotationType().equals(After.class)) { - After after = (After) annotation; - String[] tagExpressions = after.value(); - long timeout = after.timeout(); - addAfterHookDefinition(new JavaHookDefinition(method, tagExpressions, after.order(), timeout, objectFactory)); - } else if (annotation.annotationType().equals(BeforeStep.class)) { - BeforeStep beforeStep = (BeforeStep) annotation; - String[] tagExpressions = beforeStep.value(); - long timeout = beforeStep.timeout(); - addBeforeStepHookDefinition(new JavaHookDefinition(method, tagExpressions, beforeStep.order(), timeout, objectFactory)); - } else if (annotation.annotationType().equals(AfterStep.class)) { - AfterStep afterStep = (AfterStep) annotation; - String[] tagExpressions = afterStep.value(); - long timeout = afterStep.timeout(); - addAfterStepHookDefinition(new JavaHookDefinition(method, tagExpressions, afterStep.order(), timeout, objectFactory)); - } - } - } - - @Override - public void addBeforeHookDefinition(HookDefinition beforeHook) { - glue.addBeforeHook(beforeHook); - } - - @Override - public void addAfterHookDefinition(HookDefinition afterHook) { - glue.addAfterHook(afterHook); - } - - @Override - public void addAfterStepHookDefinition(HookDefinition afterStepHook) { - glue.addAfterStepHook(afterStepHook); - } - - @Override - public void addBeforeStepHookDefinition(HookDefinition beforeStepHook) { - glue.addBeforeStepHook(beforeStepHook); - - } - - - 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 { - Method regexpMethod = annotation.getClass().getMethod("timeout"); - return (Long) Utils.invoke(annotation, regexpMethod, 0); - } - -} diff --git a/java/src/main/java/cucumber/runtime/java/JavaHookDefinition.java b/java/src/main/java/cucumber/runtime/java/JavaHookDefinition.java deleted file mode 100644 index a8f9d15046..0000000000 --- a/java/src/main/java/cucumber/runtime/java/JavaHookDefinition.java +++ /dev/null @@ -1,140 +0,0 @@ -package cucumber.runtime.java; - -import cucumber.api.Scenario; -import cucumber.api.java.ObjectFactory; -import cucumber.runtime.CucumberException; -import cucumber.runtime.HookDefinition; -import cucumber.runtime.MethodFormat; -import cucumber.runtime.Utils; -import cucumber.runtime.filter.TagPredicate; -import gherkin.pickles.PickleTag; -import io.cucumber.core.event.Status; - -import java.lang.reflect.Method; -import java.util.Collection; - -import static java.util.Arrays.asList; - -public class JavaHookDefinition implements HookDefinition { - - private final Method method; - private final long timeoutMillis; - private final TagPredicate tagPredicate; - private final int order; - private final ObjectFactory objectFactory; - - JavaHookDefinition(Method method, String[] tagExpressions, int order, long timeoutMillis, ObjectFactory objectFactory) { - this.method = method; - this.timeoutMillis = timeoutMillis; - this.tagPredicate = new TagPredicate(asList(tagExpressions)); - this.order = order; - this.objectFactory = objectFactory; - } - - Method getMethod() { - return method; - } - - @Override - public String getLocation(boolean detail) { - MethodFormat format = detail ? MethodFormat.FULL : MethodFormat.SHORT; - return format.format(method); - } - - @Override - public void execute(Scenario scenario) throws Throwable { - Object[] args; - switch (method.getParameterTypes().length) { - case 0: - args = new Object[0]; - break; - case 1: - Class parameterType = method.getParameterTypes()[0]; - if(Scenario.class.equals(parameterType)) { - args = new Object[]{scenario}; - } else if(io.cucumber.core.api.Scenario.class.equals(parameterType)){ - args = new Object[]{new ScenarioAdaptor(scenario)}; - } else { - throw new CucumberException("When a hook declares an argument it must be of type " + io.cucumber.core.api.Scenario.class.getName() + ". " + method.toString()); - } - break; - default: - throw new CucumberException("Hooks must declare 0 or 1 arguments. " + method.toString()); - } - - Utils.invoke(objectFactory.getInstance(method.getDeclaringClass()), method, timeoutMillis, args); - } - - @Override - public boolean matches(Collection tags) { - return tagPredicate.apply(tags); - } - - @Override - public int getOrder() { - return order; - } - - @Override - public boolean isScenarioScoped() { - return false; - } - - private static class ScenarioAdaptor implements io.cucumber.core.api.Scenario { - private final Scenario scenario; - - ScenarioAdaptor(Scenario scenario) { - this.scenario = scenario; - } - - @Override - public Collection getSourceTagNames() { - return scenario.getSourceTagNames(); - } - - @Override - public Status getStatus() { - return Status.valueOf(scenario.getStatus().name()); - } - - @Override - public boolean isFailed() { - return scenario.isFailed(); - } - - @Override - public void embed(byte[] data, String mimeType) { - scenario.embed(data, mimeType); - } - - @Override - public void embed(byte[] data, String mimeType, String name) { - scenario.embed(data, mimeType, name); - } - - @Override - public void write(String text) { - scenario.write(text); - } - - @Override - public String getName() { - return scenario.getName(); - } - - @Override - public String getId() { - return scenario.getId(); - } - - @Override - public String getUri() { - return scenario.getUri(); - } - - @Override - public Integer getLine() { - return scenario.getLines().get(0); - } - } -} diff --git a/java/src/main/java/cucumber/runtime/java/JavaSnippet.java b/java/src/main/java/cucumber/runtime/java/JavaSnippet.java deleted file mode 100644 index 485973f38a..0000000000 --- a/java/src/main/java/cucumber/runtime/java/JavaSnippet.java +++ /dev/null @@ -1,14 +0,0 @@ -package cucumber.runtime.java; - -final class JavaSnippet extends AbstractJavaSnippet { - - @Override - public String template() { - return "" + - "@{0}(\"{1}\")\n" + - "public void {2}({3}) '{'\n" + - " // {4}\n" + - "{5} throw new cucumber.api.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 deleted file mode 100644 index 343eedda7e..0000000000 --- a/java/src/main/java/cucumber/runtime/java/JavaStepDefinition.java +++ /dev/null @@ -1,97 +0,0 @@ -package cucumber.runtime.java; - -import cucumber.api.java.ObjectFactory; -import cucumber.runtime.MethodFormat; -import cucumber.runtime.StepDefinition; -import cucumber.runtime.Utils; -import gherkin.pickles.PickleStep; -import io.cucumber.stepexpression.Argument; -import io.cucumber.stepexpression.ArgumentMatcher; -import io.cucumber.stepexpression.ExpressionArgumentMatcher; -import io.cucumber.stepexpression.StepExpression; -import io.cucumber.stepexpression.StepExpressionFactory; -import io.cucumber.stepexpression.TypeRegistry; - -import java.lang.reflect.Method; -import java.lang.reflect.Type; -import java.util.List; - -class JavaStepDefinition implements StepDefinition { - private final Method method; - private final StepExpression expression; - private final long timeoutMillis; - private final ObjectFactory objectFactory; - - private final ArgumentMatcher argumentMatcher; - private final Type[] parameterTypes; - private final String shortFormat; - private final String fullFormat; - - JavaStepDefinition(Method method, - String expression, - long timeoutMillis, - ObjectFactory objectFactory, - TypeRegistry typeRegistry) { - this.method = method; - this.timeoutMillis = timeoutMillis; - this.objectFactory = objectFactory; - List parameterInfos = ParameterInfo.fromMethod(method); - this.parameterTypes = getTypes(parameterInfos); - this.expression = createExpression(parameterInfos, expression, typeRegistry); - this.argumentMatcher = new ExpressionArgumentMatcher(this.expression); - this.shortFormat = MethodFormat.SHORT.format(method); - this.fullFormat = MethodFormat.FULL.format(method); - } - - private StepExpression createExpression(List parameterInfos, String expression, TypeRegistry typeRegistry) { - if (parameterInfos.isEmpty()) { - return new StepExpressionFactory(typeRegistry).createExpression(expression); - } else { - ParameterInfo parameterInfo = parameterInfos.get(parameterInfos.size() - 1); - return new StepExpressionFactory(typeRegistry).createExpression(expression, parameterInfo.getType(), parameterInfo.isTransposed()); - } - } - - @Override - public void execute(Object[] args) throws Throwable { - Utils.invoke(objectFactory.getInstance(method.getDeclaringClass()), method, timeoutMillis, args); - } - - @Override - public List matchedArguments(PickleStep step) { - return argumentMatcher.argumentsFrom(step, parameterTypes); - } - - private static Type[] getTypes(List parameterInfos) { - Type[] types = new Type[parameterInfos.size()]; - for (int i = 0; i < types.length; i++) { - types[i] = parameterInfos.get(i).getType(); - } - return types; - } - - @Override - public String getLocation(boolean detail) { - return detail ? fullFormat : shortFormat; - } - - @Override - public Integer getParameterCount() { - return parameterTypes.length; - } - - @Override - public boolean isDefinedAt(StackTraceElement e) { - return e.getClassName().equals(method.getDeclaringClass().getName()) && e.getMethodName().equals(method.getName()); - } - - @Override - public String getPattern() { - return expression.getSource(); - } - - @Override - public boolean isScenarioScoped() { - return false; - } -} diff --git a/java/src/main/java/cucumber/runtime/java/LambdaGlueRegistry.java b/java/src/main/java/cucumber/runtime/java/LambdaGlueRegistry.java deleted file mode 100644 index b720d6105e..0000000000 --- a/java/src/main/java/cucumber/runtime/java/LambdaGlueRegistry.java +++ /dev/null @@ -1,19 +0,0 @@ -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(Function stepDefinition); - - void addBeforeStepHookDefinition(HookDefinition beforeStepHook); - - void addAfterStepHookDefinition(HookDefinition afterStepHook); - - void addBeforeHookDefinition(HookDefinition beforeHook); - - void addAfterHookDefinition(HookDefinition afterHook); -} diff --git a/java/src/main/java/cucumber/runtime/java/MethodScanner.java b/java/src/main/java/cucumber/runtime/java/MethodScanner.java deleted file mode 100644 index 5abb4c8089..0000000000 --- a/java/src/main/java/cucumber/runtime/java/MethodScanner.java +++ /dev/null @@ -1,94 +0,0 @@ -package cucumber.runtime.java; - -import cucumber.api.java.After; -import cucumber.api.java.AfterStep; -import cucumber.api.java.Before; -import cucumber.api.java.BeforeStep; -import cucumber.runtime.ClassFinder; -import cucumber.runtime.CucumberException; -import cucumber.runtime.Utils; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.net.URI; -import java.util.List; - -class MethodScanner { - - private final ClassFinder classFinder; - - public MethodScanner(ClassFinder classFinder) { - this.classFinder = classFinder; - } - - /** - * Registers step definitions and hooks. - * - * @param javaBackend the backend where stepdefs and hooks will be registered - * @param gluePaths where to look - */ - public void scan(JavaBackend javaBackend, List gluePaths) { - for (URI gluePath : gluePaths) { - for (Class glueCodeClass : classFinder.getDescendants(Object.class, gluePath)) { - while (glueCodeClass != null && glueCodeClass != Object.class && !Utils.isInstantiable(glueCodeClass)) { - // those can't be instantiated without container class present. - glueCodeClass = glueCodeClass.getSuperclass(); - } - //prevent unnecessary checking of Object methods - if (glueCodeClass != null && glueCodeClass != Object.class) { - for (Method method : glueCodeClass.getMethods()) { - if (method.getDeclaringClass() != Object.class) { - scan(javaBackend, method, glueCodeClass); - } - } - } - } - } - } - - /** - * Registers step definitions and hooks. - * - * @param javaBackend the backend where stepdefs and hooks will be registered. - * @param method a candidate for being a stepdef or hook. - * @param glueCodeClass the class where the method is declared. - */ - public void scan(JavaBackend javaBackend, Method method, Class glueCodeClass) { - Annotation[] methodAnnotations = method.getAnnotations(); - for (Annotation annotation : methodAnnotations) { - if (isHookAnnotation(annotation)) { - validateMethod(method, glueCodeClass); - javaBackend.addHook(annotation, method); - } else if (isStepdefAnnotation(annotation)) { - validateMethod(method, glueCodeClass); - javaBackend.addStepDefinition(annotation, method); - } - } - } - - private void validateMethod(Method method, Class glueCodeClass) { - if (!method.getDeclaringClass().isAssignableFrom(glueCodeClass)) { - throw new CucumberException(String.format("%s isn't assignable from %s", method.getDeclaringClass(), glueCodeClass)); - } - if (!glueCodeClass.equals(method.getDeclaringClass())) { - throw new CucumberException(String.format("You're not allowed to extend classes that define Step Definitions or hooks. %s extends %s", glueCodeClass, method.getDeclaringClass())); - } - } - - private boolean isHookAnnotation(Annotation annotation) { - Class annotationClass = annotation.annotationType(); - return annotationClass.equals(Before.class) - || annotationClass.equals(After.class) - || annotationClass.equals(BeforeStep.class) - || annotationClass.equals(AfterStep.class) - || annotationClass.equals(io.cucumber.java.Before.class) - || annotationClass.equals(io.cucumber.java.After.class) - || annotationClass.equals(io.cucumber.java.BeforeStep.class) - || annotationClass.equals(io.cucumber.java.AfterStep.class); - } - - private boolean isStepdefAnnotation(Annotation annotation) { - Class annotationClass = annotation.annotationType(); - return annotationClass.getAnnotation(StepDefAnnotation.class) != null; - } -} diff --git a/java/src/main/java/cucumber/runtime/java/ObjectFactoryLoader.java b/java/src/main/java/cucumber/runtime/java/ObjectFactoryLoader.java deleted file mode 100644 index 79b3ced1b2..0000000000 --- a/java/src/main/java/cucumber/runtime/java/ObjectFactoryLoader.java +++ /dev/null @@ -1,162 +0,0 @@ -package cucumber.runtime.java; - -import cucumber.api.java.ObjectFactory; -import cucumber.runtime.ClassFinder; -import cucumber.runtime.CucumberException; -import cucumber.runtime.NoInstancesException; -import cucumber.runtime.Reflections; -import cucumber.runtime.TooManyInstancesException; -import io.cucumber.core.logging.Logger; -import io.cucumber.core.logging.LoggerFactory; - -import java.net.URI; -import java.util.List; - -import static java.util.Arrays.asList; -import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Objects; -import java.util.ServiceLoader; - -public final class ObjectFactoryLoader { - - private static final Logger LOG = LoggerFactory.getLogger(ObjectFactoryLoader.class); - - private ObjectFactoryLoader() { - } - - /** - * Loads an instance of {@link ObjectFactory}. - * A given class name will cause an object factory of type {@link io.cucumber.core.backend.ObjectFactory} to be loaded. - * A given deprecated class name will cause an object factory of type {@link ObjectFactory} to be loaded. - * When both are null, the implementation is searched for with a service loader or in the

cucumber.runtime
package. - * - * @param classFinder where to load classes from - * @param objectFactoryClassName specific class name of {@link io.cucumber.core.backend.ObjectFactory} implementation. May be null. - * @param deprecatedObjectFactoryClassName specific class name of {@link ObjectFactory} implementation. May be null. - * - * @return an instance of {@link ObjectFactory} - */ - public static ObjectFactory loadObjectFactory(ClassFinder classFinder, String objectFactoryClassName, String deprecatedObjectFactoryClassName) { - try { - final Reflections reflections = new Reflections(classFinder); - if ((objectFactoryClassName == null) && (deprecatedObjectFactoryClassName == null)) { - return loadSingleObjectFactory(reflections); - } else if (objectFactoryClassName != null) { - return loadSelectedObjectFactory(reflections, classFinder, objectFactoryClassName); - } else { - return loadSelectedDeprecatedObjectFactory(reflections, classFinder, deprecatedObjectFactoryClassName); - } - } catch (TooManyInstancesException e) { - LOG.warn(e.getMessage()); - LOG.warn(getMultipleObjectFactoryLogMessage()); - return new DefaultJavaObjectFactory(); - } catch (NoInstancesException e) { - return new DefaultJavaObjectFactory(); - } catch (ClassNotFoundException e) { - throw new CucumberException("Couldn't instantiate custom ObjectFactory", e); - } - } - - /** - * Loads an ObjectFactory of the type {@link io.cucumber.core.backend.ObjectFactory} either via the ServiceLoader or by searching the runtime classpath. - */ - private static ObjectFactory loadSingleObjectFactory(final Reflections reflections) { - Iterator serviceLoaderObjectFactories = ServiceLoader.load(io.cucumber.core.backend.ObjectFactory.class).iterator(); - if (serviceLoaderObjectFactories.hasNext()) { - final Collection instances = new HashSet<>(); - do { - instances.add(serviceLoaderObjectFactories.next()); - } while (serviceLoaderObjectFactories.hasNext()); - if (instances.size() > 1) { - throw new TooManyInstancesException(instances); - } - io.cucumber.core.backend.ObjectFactory objectFactory = instances.iterator().next(); - LOG.info("Loading ObjectFactory via service loader: " + objectFactory.getClass().getName() ); - return new ObjectFactoryAdapter(objectFactory); - } else { - final List packages = asList(URI.create("classpath:cucumber/runtime")); - ObjectFactory objectFactory = reflections.instantiateExactlyOneSubclass(ObjectFactory.class, packages, new Class[0], new Object[0], null); - if(objectFactory != null){ - LOG.warn("Loading deprecated ObjectFactory from runtime via reflection: " + objectFactory.getClass().getName()); - } - return objectFactory; - } - } - - /** - * Loads an ObjectFactory of the type {@link io.cucumber.core.backend.ObjectFactory} which is defined by its class name - * either via the ServiceLoader or by trying to instantiate it directly. - */ - private static ObjectFactory loadSelectedObjectFactory(final Reflections reflections, final ClassFinder classFinder, final String objectFactoryClassName) throws ClassNotFoundException { - final Iterator serviceLoaderObjectFactories = ServiceLoader.load(classFinder.loadClass(objectFactoryClassName)).iterator(); - if (serviceLoaderObjectFactories.hasNext()) { - io.cucumber.core.backend.ObjectFactory objectFactory = serviceLoaderObjectFactories.next(); - LOG.info("Loading ObjectFactory via service loader: " + objectFactory.getClass().getName() ); - return new ObjectFactoryAdapter(objectFactory); - } else { - LOG.info("Loading ObjectFactory via reflection: " + objectFactoryClassName); - return new ObjectFactoryAdapter(reflections.newInstance(new Class[0], new Object[0], classFinder.loadClass(objectFactoryClassName))); - } - } - - /** - * Loads an ObjectFactory of the deprecated type {@link cucumber.api.java.ObjectFactory} which is defined by its class name - * either via the ServiceLoader or by trying to instantiate it directly. - */ - @Deprecated - private static ObjectFactory loadSelectedDeprecatedObjectFactory(final Reflections reflections, final ClassFinder classFinder, final String objectFactoryClassName) throws ClassNotFoundException { - final Iterator serviceLoaderObjectFactories = ServiceLoader.load(classFinder.loadClass(objectFactoryClassName)).iterator(); - if (serviceLoaderObjectFactories.hasNext()) { - ObjectFactory objectFactory = serviceLoaderObjectFactories.next(); - LOG.warn("Loading deprecated ObjectFactory via service loader: " + objectFactory.getClass().getName() ); - return objectFactory; - } else { - LOG.warn("Loading deprecated ObjectFactory via reflection: " + objectFactoryClassName); - return reflections.newInstance(new Class[0], new Object[0], classFinder.loadClass(objectFactoryClassName)); - } - } - - private static String getMultipleObjectFactoryLogMessage() { - StringBuilder sb = new StringBuilder(); - sb.append("More than one Cucumber ObjectFactory was found in the classpath\n\n"); - sb.append("You probably may have included, for instance, cucumber-spring AND cucumber-guice as part of\n"); - sb.append("your dependencies. When this happens, Cucumber falls back to instantiating the\n"); - sb.append("DefaultJavaObjectFactory implementation which doesn't provide IoC.\n"); - sb.append("In order to enjoy IoC features, please remove the unnecessary dependencies from your classpath.\n"); - return sb.toString(); - } - - @Deprecated - private static class ObjectFactoryAdapter implements ObjectFactory { - - private final io.cucumber.core.backend.ObjectFactory delegate; - - public ObjectFactoryAdapter(final io.cucumber.core.backend.ObjectFactory delegate) { - this.delegate = Objects.requireNonNull(delegate); - } - - @Override - public void start() { - delegate.start(); - } - - @Override - public void stop() { - delegate.stop(); - } - - @Override - public boolean addClass(Class arg0) { - return delegate.addClass(arg0); - } - - @Override - public T getInstance(Class arg0) { - return delegate.getInstance(arg0); - } - - } - -} diff --git a/java/src/main/java/io/cucumber/java/AbstractGlueDefinition.java b/java/src/main/java/io/cucumber/java/AbstractGlueDefinition.java new file mode 100644 index 0000000000..0c7b8f2529 --- /dev/null +++ b/java/src/main/java/io/cucumber/java/AbstractGlueDefinition.java @@ -0,0 +1,37 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.Lookup; +import io.cucumber.core.reflection.MethodFormat; + +import java.lang.reflect.Method; + +abstract class AbstractGlueDefinition { + + protected final Method method; + protected final Lookup lookup; + private String shortFormat; + private String fullFormat; + + AbstractGlueDefinition(Method method, Lookup lookup) { + this.method = method; + this.lookup = lookup; + } + + public final String getLocation(boolean detail) { + return detail ? getFullLocationLocation() : getShortFormatLocation(); + } + + private String getShortFormatLocation() { + if (shortFormat == null) { + shortFormat = MethodFormat.SHORT.format(method); + } + return shortFormat; + } + + private String getFullLocationLocation() { + if (fullFormat == null) { + fullFormat = MethodFormat.FULL.format(method); + } + return fullFormat; + } +} diff --git a/java/src/main/java/cucumber/runtime/java/AbstractJavaSnippet.java b/java/src/main/java/io/cucumber/java/AbstractJavaSnippet.java similarity index 95% rename from java/src/main/java/cucumber/runtime/java/AbstractJavaSnippet.java rename to java/src/main/java/io/cucumber/java/AbstractJavaSnippet.java index 91922e2c76..ed59461ccb 100644 --- a/java/src/main/java/cucumber/runtime/java/AbstractJavaSnippet.java +++ b/java/src/main/java/io/cucumber/java/AbstractJavaSnippet.java @@ -1,6 +1,6 @@ -package cucumber.runtime.java; +package io.cucumber.java; -import cucumber.runtime.snippets.Snippet; +import io.cucumber.core.snippets.Snippet; import io.cucumber.datatable.DataTable; import java.lang.reflect.Type; diff --git a/java/src/main/java/io/cucumber/java/After.java b/java/src/main/java/io/cucumber/java/After.java index dbef92d640..7e01be9477 100644 --- a/java/src/main/java/io/cucumber/java/After.java +++ b/java/src/main/java/io/cucumber/java/After.java @@ -7,6 +7,9 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Execute method after each scenario. + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @API(status = API.Status.STABLE) @@ -22,7 +25,7 @@ /** * Duration in milliseconds this hook is allowed to run. Cucumber * will mark the hook as failed when exceeded. - * + *

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

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

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

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

+ * Valid method signatures are: + *

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

+ * Valid method signatures are: + *

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

+ * Valid method signatures are: + *

    + *
  • {@code String, Type -> Object}
  • + *
  • {@code Object, Type -> Object}
  • + *
+ * + * @see io.cucumber.cucumberexpressions.ParameterByTypeTransformer + * @see io.cucumber.cucumberexpressions.ParameterType + */ + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@API(status = API.Status.STABLE) +public @interface DefaultParameterTransformer { + +} diff --git a/java/src/main/java/io/cucumber/java/GlueAdaptor.java b/java/src/main/java/io/cucumber/java/GlueAdaptor.java new file mode 100644 index 0000000000..523f7ac651 --- /dev/null +++ b/java/src/main/java/io/cucumber/java/GlueAdaptor.java @@ -0,0 +1,82 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.Glue; +import io.cucumber.core.backend.Lookup; +import io.cucumber.core.runtime.Invoker; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +final class GlueAdaptor { + + private final Lookup lookup; + private final Glue glue; + + GlueAdaptor(Lookup lookup, Glue glue) { + this.lookup = lookup; + this.glue = glue; + } + + void addDefinition(Method method, Annotation annotation) { + if (annotation.annotationType().getAnnotation(StepDefinitionAnnotation.class) != null) { + String expression = expression(annotation); + long timeoutMillis = timeoutMillis(annotation); + glue.addStepDefinition(new JavaStepDefinition(method, expression, timeoutMillis, lookup)); + } else if (annotation.annotationType().equals(Before.class)) { + Before before = (Before) annotation; + String tagExpression = before.value(); + long timeout = before.timeout(); + glue.addBeforeHook(new JavaHookDefinition(method, tagExpression, before.order(), timeout, lookup)); + } else if (annotation.annotationType().equals(After.class)) { + After after = (After) annotation; + String tagExpression = after.value(); + long timeout = after.timeout(); + glue.addAfterHook(new JavaHookDefinition(method, tagExpression, after.order(), timeout, lookup)); + } else if (annotation.annotationType().equals(BeforeStep.class)) { + BeforeStep beforeStep = (BeforeStep) annotation; + String tagExpression = beforeStep.value(); + long timeout = beforeStep.timeout(); + glue.addBeforeStepHook(new JavaHookDefinition(method, tagExpression, beforeStep.order(), timeout, lookup)); + } else if (annotation.annotationType().equals(AfterStep.class)) { + AfterStep afterStep = (AfterStep) annotation; + String tagExpression = afterStep.value(); + long timeout = afterStep.timeout(); + glue.addAfterStepHook(new JavaHookDefinition(method, tagExpression, afterStep.order(), timeout, lookup)); + } else if (annotation.annotationType().equals(ParameterType.class)) { + ParameterType parameterType = (ParameterType) annotation; + String pattern = parameterType.value(); + String name = parameterType.name(); + boolean useForSnippets = parameterType.useForSnippets(); + boolean preferForRegexMatch = parameterType.preferForRegexMatch(); + glue.addParameterType(new JavaParameterTypeDefinition(name, pattern, method, useForSnippets, preferForRegexMatch, lookup)); + } else if (annotation.annotationType().equals(DataTableType.class)) { + glue.addDataTableType(new JavaDataTableTypeDefinition(method, lookup)); + } else if (annotation.annotationType().equals(DefaultParameterTransformer.class)) { + glue.addDefaultParameterTransformer(new JavaDefaultParameterTransformerDefinition(method, lookup)); + } else if (annotation.annotationType().equals(DefaultDataTableEntryTransformer.class)) { + glue.addDefaultDataTableEntryTransformer(new JavaDefaultDataTableEntryTransformerDefinition(method, lookup)); + } else if (annotation.annotationType().equals(DefaultDataTableCellTransformer.class)) { + glue.addDefaultDataTableCellTransformer(new JavaDefaultDataTableCellTransformerDefinition(method, lookup)); + } + } + + private static String expression(Annotation annotation) { + try { + Method expressionMethod = annotation.getClass().getMethod("value"); + return (String) Invoker.invoke(annotation, expressionMethod, 0); + } catch (Throwable e) { + throw new IllegalStateException(e); + } + } + + private static long timeoutMillis(Annotation annotation) { + try { + Method regexpMethod = annotation.getClass().getMethod("timeout"); + return (Long) Invoker.invoke(annotation, regexpMethod, 0); + } catch (Throwable throwable) { + throw new IllegalStateException(throwable); + } + } + + +} diff --git a/java/src/main/java/io/cucumber/java/InvalidMethodException.java b/java/src/main/java/io/cucumber/java/InvalidMethodException.java new file mode 100644 index 0000000000..a5b3f16a1d --- /dev/null +++ b/java/src/main/java/io/cucumber/java/InvalidMethodException.java @@ -0,0 +1,19 @@ +package io.cucumber.java; + +import io.cucumber.core.exception.CucumberException; + +import java.lang.reflect.Method; + +final class InvalidMethodException extends CucumberException { + + private InvalidMethodException(String message) { + super(message); + } + + static InvalidMethodException createInvalidMethodException(Method method, Class glueCodeClass) { + return new InvalidMethodException( + "You're not allowed to extend classes that define Step Definitions or hooks. " + + glueCodeClass + " extends " + method.getDeclaringClass()); + } + +} diff --git a/java/src/main/java/io/cucumber/java/InvalidMethodSignatureException.java b/java/src/main/java/io/cucumber/java/InvalidMethodSignatureException.java new file mode 100644 index 0000000000..66d481f268 --- /dev/null +++ b/java/src/main/java/io/cucumber/java/InvalidMethodSignatureException.java @@ -0,0 +1,87 @@ +package io.cucumber.java; + +import io.cucumber.core.exception.CucumberException; +import io.cucumber.core.reflection.MethodFormat; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +final class InvalidMethodSignatureException extends CucumberException { + + private InvalidMethodSignatureException(String message) { + super(message); + } + + static InvalidMethodSignatureExceptionBuilder builder(Method method) { + return new InvalidMethodSignatureExceptionBuilder(method); + } + + static class InvalidMethodSignatureExceptionBuilder { + + private Method method; + private final List> annotations = new ArrayList<>(); + private final List signatures = new ArrayList<>(); + private final List notes = new ArrayList<>(); + + private InvalidMethodSignatureExceptionBuilder(Method method) { + this.method = method; + } + + InvalidMethodSignatureExceptionBuilder addAnnotation(Class annotation) { + annotations.add(annotation); + return this; + } + + + InvalidMethodSignatureExceptionBuilder addSignature(String signature) { + signatures.add(signature); + return this; + } + + InvalidMethodSignatureExceptionBuilder addNote(String note) { + this.notes.add(note); + return this; + } + + public InvalidMethodSignatureException build() { + return new InvalidMethodSignatureException("" + + describeAnnotations() + " must have one of these signatures:\n" + + " * " + describeAvailableSignature() + "\n" + + "at " + describeLocation() + "\n" + + describeNote() + "\n" + ); + } + + private String describeNote() { + return String.join("\n", notes); + } + + private Object describeLocation() { + return MethodFormat.FULL.format(method); + } + + private String describeAvailableSignature() { + return String.join("\n * ", signatures); + } + + private String describeAnnotations() { + if (annotations.size() == 1) { + return "A @" + annotations.get(0).getSimpleName() + " annotated method"; + } + + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < annotations.size(); i++) { + builder.append(annotations.get(i).getSimpleName()); + + if (i < annotations.size() - 2) { + builder.append(", "); + } else if (i < annotations.size() - 1) { + builder.append(" or "); + } + } + + return "A method annotated with " + builder.toString(); + } + } +} diff --git a/java/src/main/java/io/cucumber/java/JavaBackend.java b/java/src/main/java/io/cucumber/java/JavaBackend.java new file mode 100644 index 0000000000..85558eed8b --- /dev/null +++ b/java/src/main/java/io/cucumber/java/JavaBackend.java @@ -0,0 +1,60 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.Backend; +import io.cucumber.core.backend.Container; +import io.cucumber.core.backend.Glue; +import io.cucumber.core.backend.Lookup; +import io.cucumber.core.io.ClassFinder; +import io.cucumber.core.io.ResourceLoader; +import io.cucumber.core.io.ResourceLoaderClassFinder; +import io.cucumber.core.snippets.Snippet; + +import java.net.URI; +import java.util.List; + +import static java.lang.Thread.currentThread; + +final class JavaBackend implements Backend { + + private final Lookup lookup; + private final Container container; + private final ClassFinder classFinder; + + JavaBackend(Lookup lookup, Container container, ResourceLoader resourceLoader) { + this(lookup, container, new ResourceLoaderClassFinder(resourceLoader, currentThread().getContextClassLoader())); + } + + JavaBackend(Lookup lookup, Container container, ClassFinder classFinder) { + this.lookup = lookup; + this.container = container; + this.classFinder = classFinder; + } + + @Override + public void loadGlue(Glue glue, List gluePaths) { + GlueAdaptor glueAdaptor = new GlueAdaptor(lookup, glue); + for (URI gluePath : gluePaths) { + for (Class glueCodeClass : classFinder.getDescendants(Object.class, gluePath)) { + MethodScanner.scan(glueCodeClass, (method, annotation) -> { + container.addClass(method.getDeclaringClass()); + glueAdaptor.addDefinition(method, annotation); + }); + } + } + } + + @Override + public void buildWorld() { + + } + + @Override + public void disposeWorld() { + + } + + @Override + public Snippet getSnippet() { + return new JavaSnippet(); + } +} diff --git a/java/src/main/java/io/cucumber/java/JavaBackendProviderService.java b/java/src/main/java/io/cucumber/java/JavaBackendProviderService.java new file mode 100644 index 0000000000..80d811203d --- /dev/null +++ b/java/src/main/java/io/cucumber/java/JavaBackendProviderService.java @@ -0,0 +1,15 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.Backend; +import io.cucumber.core.backend.BackendProviderService; +import io.cucumber.core.backend.Container; +import io.cucumber.core.backend.Lookup; +import io.cucumber.core.io.ResourceLoader; + +public final class JavaBackendProviderService implements BackendProviderService { + + @Override + public Backend create(Lookup lookup, Container container, ResourceLoader resourceLoader) { + return new JavaBackend(lookup, container, resourceLoader); + } +} diff --git a/java/src/main/java/io/cucumber/java/JavaDataTableTypeDefinition.java b/java/src/main/java/io/cucumber/java/JavaDataTableTypeDefinition.java new file mode 100644 index 0000000000..f10fa945c5 --- /dev/null +++ b/java/src/main/java/io/cucumber/java/JavaDataTableTypeDefinition.java @@ -0,0 +1,120 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.DataTableTypeDefinition; +import io.cucumber.core.backend.Lookup; +import io.cucumber.core.runtime.Invoker; +import io.cucumber.datatable.DataTableType; +import io.cucumber.datatable.*; + +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; + +import static io.cucumber.java.InvalidMethodSignatureException.builder; + +class JavaDataTableTypeDefinition extends AbstractGlueDefinition implements DataTableTypeDefinition { + + private final DataTableType dataTableType; + + JavaDataTableTypeDefinition(Method method, Lookup lookup) { + super(method, lookup); + this.dataTableType = createDataTableType(method); + } + + @SuppressWarnings("unchecked") + private DataTableType createDataTableType(Method method) { + Class returnType = requireValidReturnType(method); + Type parameterType = requireValidParameterType(method); + + if (DataTable.class.equals(parameterType)) { + return new DataTableType( + returnType, + (TableTransformer) this::execute + ); + } + + if (List.class.equals(parameterType)) { + return new DataTableType( + returnType, + (TableRowTransformer) this::execute + ); + } + + if (Map.class.equals(parameterType)) { + return new DataTableType( + returnType, + (TableEntryTransformer) this::execute + ); + } + + if (String.class.equals(parameterType)) { + return new DataTableType( + returnType, + (TableCellTransformer) this::execute + ); + } + + throw createInvalidSignatureException(method); + + } + + private static InvalidMethodSignatureException createInvalidSignatureException(Method method) { + return builder(method) + .addAnnotation(DataTableType.class) + .addSignature("public Author author(DataTable table)") + .addSignature("public Author author(List row)") + .addSignature("public Author author(Map entry)") + .addSignature("public Author author(String cell)") + .addNote("Note: Author is an example of the class you want to convert the table to.") + .build(); + } + + + private static Type requireValidParameterType(Method method) { + Type[] parameterTypes = method.getGenericParameterTypes(); + if (parameterTypes.length != 1) { + throw createInvalidSignatureException(method); + } + + Type parameterType = parameterTypes[0]; + if (!(parameterType instanceof ParameterizedType)) { + return parameterType; + } + + ParameterizedType parameterizedType = (ParameterizedType) parameterType; + Type[] typeParameters = parameterizedType.getActualTypeArguments(); + for (Type typeParameter : typeParameters) { + if (!String.class.equals(typeParameter)) { + throw createInvalidSignatureException(method); + } + } + + return parameterizedType.getRawType(); + } + + private static Class requireValidReturnType(Method method) { + Type returnType = method.getGenericReturnType(); + if (Void.class.equals(returnType) || void.class.equals(returnType)) { + throw createInvalidSignatureException(method); + } + + if (!(returnType instanceof Class)) { + throw createInvalidSignatureException(method); + } + + return (Class) returnType; + } + + + @Override + public DataTableType dataTableType() { + return dataTableType; + } + + private Object execute(Object arg) throws Throwable { + return Invoker.invoke(lookup.getInstance(method.getDeclaringClass()), method, 0, arg); + } + +} diff --git a/java/src/main/java/io/cucumber/java/JavaDefaultDataTableCellTransformerDefinition.java b/java/src/main/java/io/cucumber/java/JavaDefaultDataTableCellTransformerDefinition.java new file mode 100644 index 0000000000..af41ee4f47 --- /dev/null +++ b/java/src/main/java/io/cucumber/java/JavaDefaultDataTableCellTransformerDefinition.java @@ -0,0 +1,63 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.DefaultDataTableCellTransformerDefinition; +import io.cucumber.core.backend.Lookup; +import io.cucumber.core.runtime.Invoker; +import io.cucumber.datatable.TableCellByTypeTransformer; + +import java.lang.reflect.Method; +import java.lang.reflect.Type; + +import static io.cucumber.java.InvalidMethodSignatureException.builder; + +class JavaDefaultDataTableCellTransformerDefinition extends AbstractGlueDefinition implements DefaultDataTableCellTransformerDefinition { + + private final TableCellByTypeTransformer transformer; + + JavaDefaultDataTableCellTransformerDefinition(Method method, Lookup lookup) { + super(requireValidMethod(method), lookup); + this.transformer = this::execute; + } + + private static Method requireValidMethod(Method method) { + Class returnType = method.getReturnType(); + if (Void.class.equals(returnType) || void.class.equals(returnType)) { + throw createInvalidSignatureException(method); + } + + Class[] parameterTypes = method.getParameterTypes(); + if (parameterTypes.length != 2) { + throw createInvalidSignatureException(method); + } + + if (!(Object.class.equals(parameterTypes[0]) || String.class.equals(parameterTypes[0]))) { + throw createInvalidSignatureException(method); + } + + if (!Type.class.equals(parameterTypes[1])) { + throw createInvalidSignatureException(method); + } + + return method; + } + + private static InvalidMethodSignatureException createInvalidSignatureException(Method method) { + return builder(method) + .addAnnotation(DefaultDataTableCellTransformer.class) + .addSignature("public Object defaultDataTableCell(String fromValue, Type toValueType)") + .addSignature("public Object defaultDataTableCell(Object fromValue, Type toValueType)") + .build(); + } + + @Override + public TableCellByTypeTransformer tableCellByTypeTransformer() { + return transformer; + } + + + @SuppressWarnings("unchecked") + private T execute(String fromValue, Class toValueType) throws Throwable { + return (T) Invoker.invoke(lookup.getInstance(method.getDeclaringClass()), method, 0, fromValue, toValueType); + } + +} diff --git a/java/src/main/java/io/cucumber/java/JavaDefaultDataTableEntryTransformerDefinition.java b/java/src/main/java/io/cucumber/java/JavaDefaultDataTableEntryTransformerDefinition.java new file mode 100644 index 0000000000..fe0f9d71ba --- /dev/null +++ b/java/src/main/java/io/cucumber/java/JavaDefaultDataTableEntryTransformerDefinition.java @@ -0,0 +1,96 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.DefaultDataTableEntryTransformerDefinition; +import io.cucumber.core.backend.Lookup; +import io.cucumber.core.runtime.Invoker; +import io.cucumber.datatable.TableCellByTypeTransformer; +import io.cucumber.datatable.TableEntryByTypeTransformer; + +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Map; + +import static io.cucumber.java.InvalidMethodSignatureException.builder; + +class JavaDefaultDataTableEntryTransformerDefinition extends AbstractGlueDefinition implements DefaultDataTableEntryTransformerDefinition { + + private final TableEntryByTypeTransformer transformer; + + JavaDefaultDataTableEntryTransformerDefinition(Method method, Lookup lookup) { + super(requireValidMethod(method), lookup); + this.transformer = this::execute; + } + + private static Method requireValidMethod(Method method) { + Class returnType = method.getReturnType(); + if (Void.class.equals(returnType) || void.class.equals(returnType)) { + throw createInvalidSignatureException(method); + } + + Type[] parameterTypes = method.getParameterTypes(); + Type[] genericParameterTypes = method.getGenericParameterTypes(); + + if (parameterTypes.length < 2 || parameterTypes.length > 3) { + throw createInvalidSignatureException(method); + } + + Type parameterType = genericParameterTypes[0]; + + if (!Object.class.equals(parameterType)) { + if (!(parameterType instanceof ParameterizedType)) { + throw createInvalidSignatureException(method); + } + ParameterizedType parameterizedType = (ParameterizedType) parameterType; + Type rawType = parameterizedType.getRawType(); + if (!Map.class.equals(rawType)) { + throw createInvalidSignatureException(method); + } + Type[] typeParameters = parameterizedType.getActualTypeArguments(); + for (Type typeParameter : typeParameters) { + if (!String.class.equals(typeParameter)) { + throw createInvalidSignatureException(method); + } + } + } + + if (!(Type.class.equals(parameterTypes[1]) || Class.class.equals(parameterTypes[1]))) { + throw createInvalidSignatureException(method); + } + + if (parameterTypes.length == 3) { + if (!(Object.class.equals(parameterTypes[2]) || TableCellByTypeTransformer.class.equals(parameterTypes[2]))) { + throw createInvalidSignatureException(method); + } + } + + return method; + } + + private static InvalidMethodSignatureException createInvalidSignatureException(Method method) { + return builder(method) + .addAnnotation(DefaultDataTableEntryTransformer.class) + .addSignature("public T defaultDataTableEntry(Map fromValue, Class toValueType)") + .addSignature("public T defaultDataTableCell(Map fromValue, Class toValueType, TableCellByTypeTransformer cellTransformer)") + .addSignature("public Object defaultDataTableEntry(Map fromValue, Type toValueType)") + .addSignature("public Object defaultDataTableEntry(Object fromValue, Type toValueType)") + .build(); + } + + @Override + public TableEntryByTypeTransformer tableEntryByTypeTransformer() { + return transformer; + } + + @SuppressWarnings("unchecked") + private T execute(Map fromValue, Class toValueType, TableCellByTypeTransformer cellTransformer) throws Throwable { + Object[] args; + if (method.getParameterTypes().length == 3) { + args = new Object[]{fromValue, toValueType, cellTransformer}; + } else { + args = new Object[]{fromValue, toValueType}; + } + return (T) Invoker.invoke(lookup.getInstance(method.getDeclaringClass()), method, 0, args); + } + +} diff --git a/java/src/main/java/io/cucumber/java/JavaDefaultParameterTransformerDefinition.java b/java/src/main/java/io/cucumber/java/JavaDefaultParameterTransformerDefinition.java new file mode 100644 index 0000000000..ed60aba099 --- /dev/null +++ b/java/src/main/java/io/cucumber/java/JavaDefaultParameterTransformerDefinition.java @@ -0,0 +1,63 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.DefaultParameterTransformerDefinition; +import io.cucumber.core.backend.Lookup; +import io.cucumber.core.runtime.Invoker; +import io.cucumber.cucumberexpressions.ParameterByTypeTransformer; + +import java.lang.reflect.Method; +import java.lang.reflect.Type; + +import static io.cucumber.java.InvalidMethodSignatureException.builder; + +class JavaDefaultParameterTransformerDefinition extends AbstractGlueDefinition implements DefaultParameterTransformerDefinition { + + private final Lookup lookup; + private final ParameterByTypeTransformer transformer; + + JavaDefaultParameterTransformerDefinition(Method method, Lookup lookup) { + super(requireValidMethod(method), lookup); + this.lookup = lookup; + this.transformer = this::execute; + } + + private static Method requireValidMethod(Method method) { + Class returnType = method.getReturnType(); + if (Void.class.equals(returnType) || void.class.equals(returnType)) { + throw createInvalidSignatureException(method); + } + + Class[] parameterTypes = method.getParameterTypes(); + if (parameterTypes.length != 2) { + throw createInvalidSignatureException(method); + } + + if (!(Object.class.equals(parameterTypes[0]) || String.class.equals(parameterTypes[0]))) { + throw createInvalidSignatureException(method); + } + + if (!Type.class.equals(parameterTypes[1])) { + throw createInvalidSignatureException(method); + } + + return method; + } + + private static InvalidMethodSignatureException createInvalidSignatureException(Method method) { + return builder(method) + .addAnnotation(DefaultParameterTransformer.class) + .addSignature("public Object defaultDataTableEntry(String fromValue, Type toValueType)") + .addSignature("public Object defaultDataTableEntry(Object fromValue, Type toValueType)") + .build(); + } + + @Override + public ParameterByTypeTransformer parameterByTypeTransformer() { + return transformer; + } + + private Object execute(String fromValue, Type toValueType) throws Throwable { + return Invoker.invoke(lookup.getInstance(method.getDeclaringClass()), method, 0, fromValue, toValueType); + } + +} diff --git a/java/src/main/java/io/cucumber/java/JavaHookDefinition.java b/java/src/main/java/io/cucumber/java/JavaHookDefinition.java new file mode 100644 index 0000000000..6b825d7aea --- /dev/null +++ b/java/src/main/java/io/cucumber/java/JavaHookDefinition.java @@ -0,0 +1,77 @@ +package io.cucumber.java; + +import gherkin.pickles.PickleTag; +import io.cucumber.core.api.Scenario; +import io.cucumber.core.backend.HookDefinition; +import io.cucumber.core.backend.Lookup; +import io.cucumber.core.filter.TagPredicate; +import io.cucumber.core.runtime.Invoker; + +import java.lang.reflect.Method; +import java.util.Collection; + +import static io.cucumber.java.InvalidMethodSignatureException.builder; + +final class JavaHookDefinition extends AbstractGlueDefinition implements HookDefinition { + + private final long timeoutMillis; + private final TagPredicate tagPredicate; + private final int order; + private final Lookup lookup; + + JavaHookDefinition(Method method, String tagExpression, int order, long timeoutMillis, Lookup lookup) { + super(requireValidMethod(method), lookup); + this.timeoutMillis = timeoutMillis; + this.tagPredicate = new TagPredicate(tagExpression); + this.order = order; + this.lookup = lookup; + } + + private static Method requireValidMethod(Method method) { + Class[] parameterTypes = method.getParameterTypes(); + if (parameterTypes.length > 1) { + throw createInvalidSignatureException(method); + } + + if (parameterTypes.length == 1) { + if (!(Object.class.equals(parameterTypes[0]) || Scenario.class.equals(parameterTypes[0]))) { + throw createInvalidSignatureException(method); + } + } + + return method; + } + + private static InvalidMethodSignatureException createInvalidSignatureException(Method method) { + return builder(method) + .addAnnotation(Before.class) + .addAnnotation(After.class) + .addAnnotation(BeforeStep.class) + .addAnnotation(AfterStep.class) + .addSignature("public void before_or_after(Scenario scenario)") + .addSignature("public void before_or_after()") + .build(); + } + + @Override + public void execute(Scenario scenario) throws Throwable { + Object[] args; + if (method.getParameterTypes().length == 1) { + args = new Object[]{scenario}; + } else { + args = new Object[0]; + } + + Invoker.invoke(lookup.getInstance(method.getDeclaringClass()), method, timeoutMillis, args); + } + + @Override + public boolean matches(Collection tags) { + return tagPredicate.apply(tags); + } + + @Override + public int getOrder() { + return order; + } +} diff --git a/java/src/main/java/cucumber/runtime/java/ParameterInfo.java b/java/src/main/java/io/cucumber/java/JavaParameterInfo.java similarity index 60% rename from java/src/main/java/cucumber/runtime/java/ParameterInfo.java rename to java/src/main/java/io/cucumber/java/JavaParameterInfo.java index 525e11b93f..2754be58e1 100644 --- a/java/src/main/java/cucumber/runtime/java/ParameterInfo.java +++ b/java/src/main/java/io/cucumber/java/JavaParameterInfo.java @@ -1,6 +1,7 @@ -package cucumber.runtime.java; +package io.cucumber.java; -import cucumber.api.Transpose; +import io.cucumber.core.backend.ParameterInfo; +import io.cucumber.core.backend.TypeResolver; import java.lang.annotation.Annotation; import java.lang.reflect.Method; @@ -11,44 +12,42 @@ /** * This class composes all interesting parameter information into one object. */ -class ParameterInfo { +class JavaParameterInfo implements ParameterInfo { private final Type type; private final boolean transposed; static List fromMethod(Method method) { - List result = new ArrayList(); + List result = new ArrayList<>(); Type[] genericParameterTypes = method.getGenericParameterTypes(); Annotation[][] annotations = method.getParameterAnnotations(); for (int i = 0; i < genericParameterTypes.length; i++) { boolean transposed = false; for (Annotation annotation : annotations[i]) { - if (annotation instanceof Transpose ) { + if (annotation instanceof Transpose) { transposed = ((Transpose) annotation).value(); - } else if (annotation instanceof io.cucumber.java.Transpose) { - transposed = ((io.cucumber.java.Transpose) annotation).value(); } } - result.add(new ParameterInfo(genericParameterTypes[i], transposed)); + result.add(new JavaParameterInfo(genericParameterTypes[i], transposed)); } return result; } - private ParameterInfo(Type type, boolean transposed) { + private JavaParameterInfo(Type type, boolean transposed) { this.type = type; this.transposed = transposed; } - Type getType() { + public Type getType() { return type; } - boolean isTransposed() { + public boolean isTransposed() { return transposed; } @Override - public String toString() { - return type.toString(); + public TypeResolver getTypeResolver() { + return () -> type; } } diff --git a/java/src/main/java/io/cucumber/java/JavaParameterTypeDefinition.java b/java/src/main/java/io/cucumber/java/JavaParameterTypeDefinition.java new file mode 100644 index 0000000000..f92f4c8d74 --- /dev/null +++ b/java/src/main/java/io/cucumber/java/JavaParameterTypeDefinition.java @@ -0,0 +1,87 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.Lookup; +import io.cucumber.core.backend.ParameterTypeDefinition; +import io.cucumber.core.runtime.Invoker; +import io.cucumber.cucumberexpressions.ParameterType; + +import java.lang.reflect.Method; +import java.lang.reflect.Type; + +import static io.cucumber.java.InvalidMethodSignatureException.builder; +import static java.util.Collections.singletonList; + +class JavaParameterTypeDefinition extends AbstractGlueDefinition implements ParameterTypeDefinition { + + private final ParameterType parameterType; + + JavaParameterTypeDefinition(String name, String pattern, Method method, boolean useForSnippets, boolean preferForRegexpMatch, Lookup lookup) { + super(requireValidMethod(method), lookup); + this.parameterType = new ParameterType<>( + name.isEmpty() ? method.getName() : name, + singletonList(pattern), + this.method.getReturnType(), + this::execute, + useForSnippets, + preferForRegexpMatch + ); + } + + private static Method requireValidMethod(Method method) { + Type returnType = method.getGenericReturnType(); + if (Void.class.equals(returnType) || void.class.equals(returnType)) { + throw createInvalidSignatureException(method); + } + if (!(returnType instanceof Class)) { + throw createInvalidSignatureException(method); + } + + Type[] parameterTypes = method.getGenericParameterTypes(); + if (parameterTypes.length == 0) { + throw createInvalidSignatureException(method); + } + + if (parameterTypes.length == 1) { + if (!(String.class.equals(parameterTypes[0]) || String[].class.equals(parameterTypes[0]))) { + throw createInvalidSignatureException(method); + } + return method; + } + + for (Type parameterType : parameterTypes) { + if (!String.class.equals(parameterType)) { + throw createInvalidSignatureException(method); + } + } + + return method; + } + + private static InvalidMethodSignatureException createInvalidSignatureException(Method method) { + return builder(method) + .addAnnotation(ParameterType.class) + .addSignature("public Author parameterName(String all)") + .addSignature("public Author parameterName(String captureGroup1, String captureGroup2, ...ect )") + .addSignature("public Author parameterName(String... captureGroups)") + .addNote("Note: Author is an example of the class you want to convert captureGroups to") + .build(); + } + + @Override + public ParameterType parameterType() { + return parameterType; + } + + private Object execute(String[] captureGroups) throws Throwable { + Object[] args; + + if (String[].class.equals(method.getParameterTypes()[0])) { + args = new Object[][]{captureGroups}; + } else { + args = captureGroups; + } + + return Invoker.invoke(lookup.getInstance(method.getDeclaringClass()), method, 0, args); + } + +} diff --git a/java/src/main/java/io/cucumber/java/JavaSnippet.java b/java/src/main/java/io/cucumber/java/JavaSnippet.java new file mode 100644 index 0000000000..1c21f30187 --- /dev/null +++ b/java/src/main/java/io/cucumber/java/JavaSnippet.java @@ -0,0 +1,16 @@ +package io.cucumber.java; + +import java.text.MessageFormat; + +final class JavaSnippet extends AbstractJavaSnippet { + + @Override + public MessageFormat template() { + return new MessageFormat("" + + "@{0}(\"{1}\")\n" + + "public void {2}({3}) '{'\n" + + " // {4}\n" + + "{5} throw new " + PendingException.class.getName() + "();\n" + + "'}'\n"); + } +} diff --git a/java/src/main/java/io/cucumber/java/JavaStepDefinition.java b/java/src/main/java/io/cucumber/java/JavaStepDefinition.java new file mode 100644 index 0000000000..65d2942148 --- /dev/null +++ b/java/src/main/java/io/cucumber/java/JavaStepDefinition.java @@ -0,0 +1,47 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.Lookup; +import io.cucumber.core.backend.ParameterInfo; +import io.cucumber.core.backend.StepDefinition; +import io.cucumber.core.runtime.Invoker; + +import java.lang.reflect.Method; +import java.util.List; + +final class JavaStepDefinition extends AbstractGlueDefinition implements StepDefinition { + private final String expression; + private final long timeoutMillis; + + private final List parameterInfos; + + JavaStepDefinition(Method method, + String expression, + long timeoutMillis, + Lookup lookup) { + super(method, lookup); + this.timeoutMillis = timeoutMillis; + this.parameterInfos = JavaParameterInfo.fromMethod(method); + this.expression = expression; + } + + @Override + public void execute(Object[] args) throws Throwable { + Invoker.invoke(lookup.getInstance(method.getDeclaringClass()), method, timeoutMillis, args); + } + + @Override + public boolean isDefinedAt(StackTraceElement e) { + return e.getClassName().equals(method.getDeclaringClass().getName()) && e.getMethodName().equals(method.getName()); + } + + @Override + public String getPattern() { + return expression; + } + + @Override + public List parameterInfos() { + return parameterInfos; + } + +} diff --git a/java/src/main/java/io/cucumber/java/MethodScanner.java b/java/src/main/java/io/cucumber/java/MethodScanner.java new file mode 100644 index 0000000000..e93deeb9c7 --- /dev/null +++ b/java/src/main/java/io/cucumber/java/MethodScanner.java @@ -0,0 +1,90 @@ +package io.cucumber.java; + +import io.cucumber.core.runtime.Invoker; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.function.BiConsumer; + +import static io.cucumber.core.reflection.Reflections.isInstantiable; +import static io.cucumber.java.InvalidMethodException.createInvalidMethodException; + +final class MethodScanner { + + private MethodScanner() { + } + + static void scan(Class aClass, BiConsumer consumer) { + // prevent unnecessary checking of Object methods + if (Object.class.equals(aClass)) { + return; + } + + if (!isInstantiable(aClass)) { + return; + } + for (Method method : aClass.getMethods()) { + scan(consumer, aClass, method); + } + } + + private static void scan(BiConsumer consumer, Class aClass, Method method) { + //prevent unnecessary checking of Object methods + if (Object.class.equals(method.getDeclaringClass())) { + return; + } + scan(consumer, aClass, method, method.getAnnotations()); + } + + private static void scan(BiConsumer consumer, Class aClass, Method method, Annotation[] methodAnnotations) { + for (Annotation annotation : methodAnnotations) { + if (isHookAnnotation(annotation) || isStepDefinitionAnnotation(annotation)) { + validateMethod(aClass, method); + consumer.accept(method, annotation); + } else if (isRepeatedStepDefinitionAnnotation(annotation)) { + scan(consumer, aClass, method, repeatedAnnotations(annotation)); + } + } + } + + private static void validateMethod(Class glueCodeClass, Method method) { + if (!glueCodeClass.equals(method.getDeclaringClass())) { + throw createInvalidMethodException(method, glueCodeClass); + } + } + + private static boolean isHookAnnotation(Annotation annotation) { + Class annotationClass = annotation.annotationType(); + return annotationClass.equals(Before.class) + || annotationClass.equals(After.class) + || annotationClass.equals(BeforeStep.class) + || annotationClass.equals(AfterStep.class) + || annotationClass.equals(ParameterType.class) + || annotationClass.equals(DataTableType.class) + || annotationClass.equals(DefaultParameterTransformer.class) + || annotationClass.equals(DefaultDataTableEntryTransformer.class) + || annotationClass.equals(DefaultDataTableCellTransformer.class) + ; + } + + private static boolean isStepDefinitionAnnotation(Annotation annotation) { + Class annotationClass = annotation.annotationType(); + return annotationClass.getAnnotation(StepDefinitionAnnotation.class) != null; + } + + + private static boolean isRepeatedStepDefinitionAnnotation(Annotation annotation) { + Class annotationClass = annotation.annotationType(); + return annotationClass.getAnnotation(StepDefinitionAnnotations.class) != null; + } + + private static Annotation[] repeatedAnnotations(Annotation annotation) { + try { + Method expressionMethod = annotation.getClass().getMethod("value"); + return (Annotation[]) Invoker.invoke(annotation, expressionMethod, 0); + } catch (Throwable e) { + throw new IllegalStateException(e); + } + } + +} diff --git a/java/src/main/java/io/cucumber/java/ParameterType.java b/java/src/main/java/io/cucumber/java/ParameterType.java new file mode 100644 index 0000000000..ee052c5d54 --- /dev/null +++ b/java/src/main/java/io/cucumber/java/ParameterType.java @@ -0,0 +1,79 @@ +package io.cucumber.java; + +import io.cucumber.cucumberexpressions.GeneratedExpression; +import io.cucumber.cucumberexpressions.RegularExpression; +import org.apiguardian.api.API; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Register parameter type. + *

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

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

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

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

+ * This is used in the type name in typed expressions. When not provided this will default to the name of + * the annotated method. + * + * @return human readable type name + * @see io.cucumber.cucumberexpressions.ParameterType#getName() + */ + String name() default ""; + + /** + * Indicates whether or not this is a preferential parameter type when matching text + * against a {@link RegularExpression}. In case there are multiple parameter types + * with a regexp identical to the capture group's regexp, a preferential parameter type will + * win. If there are more than 1 preferential ones, an error will be thrown. + * + * @return true if this is a preferential type + * @see io.cucumber.cucumberexpressions.ParameterType#preferForRegexpMatch() + */ + boolean preferForRegexMatch() default false; + + /** + * Indicates whether or not this is a parameter type that should be used for generating + * {@link GeneratedExpression}s from text. Typically, parameter types with greedy regexps + * should return false. + * + * @return true is this parameter type is used for expression generation + * @see io.cucumber.cucumberexpressions.ParameterType#useForSnippets() + */ + boolean useForSnippets() default false; +} diff --git a/java/src/main/java/io/cucumber/java/PendingException.java b/java/src/main/java/io/cucumber/java/PendingException.java new file mode 100644 index 0000000000..3858c999a2 --- /dev/null +++ b/java/src/main/java/io/cucumber/java/PendingException.java @@ -0,0 +1,21 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.Pending; +import org.apiguardian.api.API; + +/** + * When thrown from a step marks it as not yet implemented. + * + * @see JavaSnippet + */ +@Pending +@API(status = API.Status.STABLE) +public final class PendingException extends RuntimeException { + public PendingException() { + this("TODO: implement me"); + } + + public PendingException(String message) { + super(message); + } +} diff --git a/java/src/main/java/cucumber/runtime/java/StepDefAnnotation.java b/java/src/main/java/io/cucumber/java/StepDefinitionAnnotation.java similarity index 63% rename from java/src/main/java/cucumber/runtime/java/StepDefAnnotation.java rename to java/src/main/java/io/cucumber/java/StepDefinitionAnnotation.java index 9d16b0db08..ca63b55e78 100644 --- a/java/src/main/java/cucumber/runtime/java/StepDefAnnotation.java +++ b/java/src/main/java/io/cucumber/java/StepDefinitionAnnotation.java @@ -1,4 +1,6 @@ -package cucumber.runtime.java; +package io.cucumber.java; + +import org.apiguardian.api.API; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -7,5 +9,6 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) -public @interface StepDefAnnotation { +@API(status = API.Status.INTERNAL) +public @interface StepDefinitionAnnotation { } diff --git a/java/src/main/java/io/cucumber/java/StepDefinitionAnnotations.java b/java/src/main/java/io/cucumber/java/StepDefinitionAnnotations.java new file mode 100644 index 0000000000..69c2b1a5c4 --- /dev/null +++ b/java/src/main/java/io/cucumber/java/StepDefinitionAnnotations.java @@ -0,0 +1,14 @@ +package io.cucumber.java; + +import org.apiguardian.api.API; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.ANNOTATION_TYPE) +@API(status = API.Status.INTERNAL) +public @interface StepDefinitionAnnotations { +} diff --git a/java/src/main/resources/META-INF/services/io.cucumber.core.backend.BackendProviderService b/java/src/main/resources/META-INF/services/io.cucumber.core.backend.BackendProviderService new file mode 100644 index 0000000000..6bb2a061c2 --- /dev/null +++ b/java/src/main/resources/META-INF/services/io.cucumber.core.backend.BackendProviderService @@ -0,0 +1 @@ +io.cucumber.java.JavaBackendProviderService \ 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 deleted file mode 100644 index 35a58e0f09..0000000000 --- a/java/src/test/java/cucumber/runtime/java/JavaBackendTest.java +++ /dev/null @@ -1,112 +0,0 @@ -package cucumber.runtime.java; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import cucumber.api.java.ObjectFactory; -import cucumber.runtime.CucumberException; -import cucumber.runtime.Env; -import cucumber.runtime.Glue; -import cucumber.runtime.HookDefinition; -import cucumber.runtime.StepDefinition; -import cucumber.runtime.io.MultiLoader; -import cucumber.runtime.io.ResourceLoader; -import cucumber.runtime.io.ResourceLoaderClassFinder; -import cucumber.runtime.java.stepdefs.Stepdefs; -import io.cucumber.stepexpression.TypeRegistry; -import org.junit.Before; -import org.junit.Test; - -import java.net.URI; -import java.util.Locale; - -import static java.lang.Thread.currentThread; -import static java.util.Arrays.asList; -import static org.junit.Assert.*; - -public class JavaBackendTest { - - private ObjectFactory factory; - private JavaBackend backend; - - @Before - public void createBackend() { - ClassLoader classLoader = currentThread().getContextClassLoader(); - ResourceLoader resourceLoader = new MultiLoader(classLoader); - ResourceLoaderClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader); - this.factory = new DefaultJavaObjectFactory(); - TypeRegistry typeRegistry = new TypeRegistry(Locale.ENGLISH); - this.backend = new JavaBackend(factory, classFinder, typeRegistry); - } - - @Test - public void finds_step_definitions_by_classpath_url() { - GlueStub glue = new GlueStub(); - backend.loadGlue(glue, asList(URI.create("classpath:cucumber/runtime/java/stepdefs"))); - backend.buildWorld(); - assertEquals(Stepdefs.class, factory.getInstance(Stepdefs.class).getClass()); - } - - @Test(expected = CucumberException.class) - public void detects_subclassed_glue_and_throws_exception() { - GlueStub glue = new GlueStub(); - backend.loadGlue(glue, asList( - URI.create("classpath:cucumber/runtime/java/stepdefs"), - URI.create("classpath:cucumber/runtime/java/incorrectlysubclassedstepdefs")) - ); - } - - @Test - public void testObjectFactoryClassNameWhenNotSpecified() { - assertNull(JavaBackend.getObjectFactoryClassName(mock(Env.class))); - } - - @Test - public void testGetObjectFactoryClassName() { - Env env = when(mock(Env.class).get(JavaBackend.OBJECT_FACTORY_KEY)).thenReturn("AN_OBJECT_FACTORY").getMock(); - assertEquals("AN_OBJECT_FACTORY", JavaBackend.getObjectFactoryClassName(env)); - } - - @Test - public void testDeprecatedObjectFactoryClassNameWhenNotSpecified() { - assertNull(JavaBackend.getDeprecatedObjectFactoryClassName(mock(Env.class))); - } - - @Test - public void testGetDeprecatedObjectFactoryClassName() { - Env env = when(mock(Env.class).get(ObjectFactory.class.getName())).thenReturn("DEPRECATED_OBJECT_FACTORY").getMock(); - assertEquals("DEPRECATED_OBJECT_FACTORY", JavaBackend.getDeprecatedObjectFactoryClassName(env)); - } - - private class GlueStub implements Glue { - - @Override - public void addStepDefinition(StepDefinition stepDefinition) { - //no-op - } - - @Override - public void addBeforeStepHook(HookDefinition beforeStepHook) { - throw new UnsupportedOperationException(); - } - - @Override - public void addBeforeHook(HookDefinition hookDefinition) { - throw new UnsupportedOperationException(); - } - - @Override - public void addAfterStepHook(HookDefinition hookDefinition) { - throw new UnsupportedOperationException(); - } - - @Override - public void addAfterHook(HookDefinition hookDefinition) { - throw new UnsupportedOperationException(); - } - - @Override - public void removeScenarioScopedGlue() { - } - } -} diff --git a/java/src/test/java/cucumber/runtime/java/JavaHookTest.java b/java/src/test/java/cucumber/runtime/java/JavaHookTest.java deleted file mode 100644 index 219df49605..0000000000 --- a/java/src/test/java/cucumber/runtime/java/JavaHookTest.java +++ /dev/null @@ -1,223 +0,0 @@ -package cucumber.runtime.java; - -import cucumber.api.Scenario; -import cucumber.api.java.After; -import cucumber.api.java.AfterStep; -import cucumber.api.java.Before; -import cucumber.api.java.BeforeStep; -import cucumber.runtime.ClassFinder; -import cucumber.runtime.Glue; -import cucumber.runtime.HookDefinition; -import cucumber.runtime.io.MultiLoader; -import cucumber.runtime.io.ResourceLoader; -import cucumber.runtime.io.ResourceLoaderClassFinder; -import gherkin.pickles.PickleLocation; -import gherkin.pickles.PickleTag; -import io.cucumber.stepexpression.TypeRegistry; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.mockito.ArgumentCaptor; -import org.mockito.ArgumentMatcher; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; - -import java.lang.reflect.Method; -import java.net.URI; -import java.util.Collections; -import java.util.Locale; - -import static java.util.Arrays.asList; -import static org.mockito.AdditionalMatchers.not; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.quality.Strictness.STRICT_STUBS; - -public class JavaHookTest { - - private final PickleTag pickleTagBar = new PickleTag(mock(PickleLocation.class), "@bar"); - private final PickleTag pickleTagZap = new PickleTag(mock(PickleLocation.class), "@zap"); - @Rule - public MockitoRule mockitoRule = MockitoJUnit.rule().strictness(STRICT_STUBS); - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - private static final Method BEFORE; - private static final Method AFTER; - private static final Method BEFORESTEP; - private static final Method AFTERSTEP; - private static final Method BAD_AFTER; - - static { - try { - BEFORE = HasHooks.class.getMethod("before"); - AFTER = HasHooks.class.getMethod("after"); - BEFORESTEP = HasHooks.class.getMethod("beforeStep"); - AFTERSTEP = HasHooks.class.getMethod("afterStep"); - BAD_AFTER = BadHook.class.getMethod("after", String.class); - } catch (NoSuchMethodException note) { - throw new InternalError("dang"); - } - } - - @Mock - private Glue glue; - - private JavaBackend backend; - - private SingletonFactory objectFactory; - - @org.junit.Before - public void createBackendAndLoadNoGlue() { - this.objectFactory = new SingletonFactory(); - - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - ResourceLoader resourceLoader = new MultiLoader(classLoader); - ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader); - TypeRegistry typeRegistry = new TypeRegistry(Locale.ENGLISH); - this.backend = new JavaBackend(objectFactory, classFinder, typeRegistry); - backend.loadGlue(glue, Collections.emptyList()); - } - - @Test - public void before_hooks_get_registered() { - objectFactory.setInstance(new HasHooks()); - backend.buildWorld(); - backend.addHook(BEFORE.getAnnotation(Before.class), BEFORE); - - verify(glue).addBeforeHook(argThat(isHookFor(BEFORE))); - } - - private static ArgumentMatcher isHookFor(final Method method) { - return new ArgumentMatcher() { - @Override - public boolean matches(JavaHookDefinition javaHookDefinition) { - return method.equals(javaHookDefinition.getMethod()); - } - }; - } - - @Test - public void before_step_hooks_get_registered() { - objectFactory.setInstance(new HasHooks()); - backend.buildWorld(); - backend.addHook(BEFORESTEP.getAnnotation(BeforeStep.class), BEFORESTEP); - - verify(glue).addBeforeStepHook(argThat(isHookFor(BEFORESTEP))); - } - - @Test - public void after_step_hooks_get_registered() { - objectFactory.setInstance(new HasHooks()); - backend.buildWorld(); - backend.addHook(AFTERSTEP.getAnnotation(AfterStep.class), AFTERSTEP); - verify(glue).addAfterStepHook(argThat(isHookFor(AFTERSTEP))); - } - - @Test - public void after_hooks_get_registered() { - objectFactory.setInstance(new HasHooks()); - backend.buildWorld(); - backend.addHook(AFTER.getAnnotation(After.class), AFTER); - verify(glue).addAfterHook(argThat(isHookFor(AFTER))); - - } - - @Test - public void hook_order_gets_registered() { - objectFactory.setInstance(new HasHooks()); - backend.buildWorld(); - backend.addHook(AFTER.getAnnotation(After.class), AFTER); - verify(glue).addAfterHook(argThat(isHookWithOrder(1))); - - } - - private static ArgumentMatcher isHookWithOrder(final int order) { - return new ArgumentMatcher() { - @Override - public boolean matches(JavaHookDefinition argument) { - return argument.getOrder() == order; - } - }; - } - - @Test - public void hook_with_no_order_is_last() { - objectFactory.setInstance(new HasHooks()); - backend.buildWorld(); - backend.addHook(BEFORE.getAnnotation(Before.class), BEFORE); - verify(glue).addBeforeHook(argThat(isHookWithOrder(10000))); - } - - @Test - public void matches_matching_tags() { - objectFactory.setInstance(new HasHooks()); - backend.buildWorld(); - backend.addHook(BEFORE.getAnnotation(Before.class), BEFORE); - verify(glue).addBeforeHook(argThat(isHookThatMatches(pickleTagBar, pickleTagZap))); - } - - private static ArgumentMatcher isHookThatMatches(final PickleTag... pickleTag) { - return new ArgumentMatcher() { - @Override - public boolean matches(JavaHookDefinition argument) { - return argument.matches(asList(pickleTag)); - } - }; - } - - @Test - public void does_not_match_non_matching_tags() { - objectFactory.setInstance(new HasHooks()); - backend.buildWorld(); - backend.addHook(BEFORE.getAnnotation(Before.class), BEFORE); - verify(glue).addBeforeHook(not(argThat(isHookThatMatches(pickleTagBar)))); - } - - @Test - public void fails_if_hook_argument_is_not_scenario_result() throws Throwable { - objectFactory.setInstance(new BadHook()); - backend.buildWorld(); - backend.addHook(BAD_AFTER.getAnnotation(After.class), BAD_AFTER); - - ArgumentCaptor javaHookDefinitionArgumentCaptor = ArgumentCaptor.forClass(JavaHookDefinition.class); - verify(glue).addAfterHook(javaHookDefinitionArgumentCaptor.capture()); - - HookDefinition bad = javaHookDefinitionArgumentCaptor.getValue(); - expectedException.expectMessage("When a hook declares an argument it must be of type io.cucumber.core.api.Scenario. public void cucumber.runtime.java.JavaHookTest$BadHook.after(java.lang.String)"); - bad.execute(mock(Scenario.class)); - } - - public static class HasHooks { - - @Before({"(@foo or @bar) and @zap"}) - public void before() { - - } - - @BeforeStep - public void beforeStep() { - - } - - @AfterStep - public void afterStep() { - - } - - @After(order = 1) - public void after() { - - } - } - - public static class BadHook { - @After - public void after(String badType) { - - } - } -} diff --git a/java/src/test/java/cucumber/runtime/java/JavaStepDefinitionTest.java b/java/src/test/java/cucumber/runtime/java/JavaStepDefinitionTest.java deleted file mode 100644 index ded6fd388a..0000000000 --- a/java/src/test/java/cucumber/runtime/java/JavaStepDefinitionTest.java +++ /dev/null @@ -1,139 +0,0 @@ -package cucumber.runtime.java; - -import cucumber.api.Result; -import cucumber.api.event.EventHandler; -import cucumber.api.event.TestStepFinished; -import cucumber.api.java.ObjectFactory; -import cucumber.api.java.en.Given; -import cucumber.runner.TimeServiceEventBus; -import cucumber.runner.EventBus; -import cucumber.runner.Runner; -import cucumber.runner.TimeService; -import cucumber.runner.AmbiguousStepDefinitionsException; -import cucumber.runtime.Backend; -import cucumber.runtime.BackendSupplier; -import io.cucumber.core.options.RuntimeOptions; -import cucumber.runtime.DuplicateStepDefinitionException; -import cucumber.runner.ThreadLocalRunnerSupplier; -import cucumber.runtime.io.MultiLoader; -import cucumber.runtime.io.ResourceLoader; -import cucumber.runtime.io.ResourceLoaderClassFinder; -import gherkin.events.PickleEvent; -import gherkin.pickles.Argument; -import gherkin.pickles.Pickle; -import gherkin.pickles.PickleLocation; -import gherkin.pickles.PickleStep; -import gherkin.pickles.PickleTag; -import org.junit.Before; -import org.junit.Test; - -import java.lang.reflect.Method; -import java.util.Collection; -import java.util.Collections; -import java.util.Locale; - -import io.cucumber.stepexpression.TypeRegistry; - -import static java.lang.Thread.currentThread; -import static java.util.Arrays.asList; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; - -public class JavaStepDefinitionTest { - private static final Method THREE_DISABLED_MICE; - private static final Method THREE_BLIND_ANIMALS; - private static final String ENGLISH = "en"; - - static { - try { - THREE_DISABLED_MICE = Defs.class.getMethod("threeDisabledMice", String.class); - THREE_BLIND_ANIMALS = Defs.class.getMethod("threeBlindAnimals", String.class); - } catch (NoSuchMethodException e) { - throw new InternalError("dang"); - } - } - - private final Defs defs = new Defs(); - private JavaBackend backend; - private Result latestReceivedResult; - private Runner runner; - - @Before - public void createBackendAndLoadNoGlue() { - ClassLoader classLoader = currentThread().getContextClassLoader(); - ResourceLoader resourceLoader = new MultiLoader(classLoader); - ResourceLoaderClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader); - ObjectFactory factory = new SingletonFactory(defs); - TypeRegistry typeRegistry = new TypeRegistry(Locale.ENGLISH); - this.backend = new JavaBackend(factory, classFinder, typeRegistry); - RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); - EventBus bus = new TimeServiceEventBus(TimeService.SYSTEM); - BackendSupplier backendSupplier = new BackendSupplier() { - @Override - public Collection get() { - return asList(backend); - } - }; - this.runner = new ThreadLocalRunnerSupplier(runtimeOptions, bus, backendSupplier).get(); - - bus.registerHandlerFor(TestStepFinished.class, new EventHandler() { - @Override - public void receive(TestStepFinished event) { - latestReceivedResult = event.result; - } - }); - } - - @Test(expected = DuplicateStepDefinitionException.class) - public void throws_duplicate_when_two_stepdefs_with_same_regexp_found() throws Throwable { - backend.addStepDefinition(THREE_BLIND_ANIMALS.getAnnotation(Given.class), THREE_DISABLED_MICE); - backend.addStepDefinition(THREE_BLIND_ANIMALS.getAnnotation(Given.class), THREE_BLIND_ANIMALS); - } - - @Test - public void throws_ambiguous_when_two_matches_are_found() throws Throwable { - backend.addStepDefinition(THREE_DISABLED_MICE.getAnnotation(Given.class), THREE_DISABLED_MICE); - backend.addStepDefinition(THREE_BLIND_ANIMALS.getAnnotation(Given.class), THREE_BLIND_ANIMALS); - - PickleTag tag = new PickleTag(mock(PickleLocation.class), "@foo"); - PickleStep step = new PickleStep("three blind mice", Collections.emptyList(), asList(mock(PickleLocation.class))); - Pickle pickle = new Pickle("pickle name", ENGLISH, asList(step), asList(tag), asList(mock(PickleLocation.class))); - PickleEvent pickleEvent = new PickleEvent("uri", pickle); - runner.runPickle(pickleEvent); - - assertEquals(AmbiguousStepDefinitionsException.class, latestReceivedResult.getError().getClass()); - } - - @Test - public void does_not_throw_ambiguous_when_nothing_is_ambiguous() throws Throwable { - backend.addStepDefinition(THREE_DISABLED_MICE.getAnnotation(Given.class), THREE_DISABLED_MICE); - - PickleTag tag = new PickleTag(mock(PickleLocation.class), "@foo"); - PickleStep step = new PickleStep("three blind mice", Collections.emptyList(), asList(mock(PickleLocation.class))); - Pickle pickle = new Pickle("pickle name", ENGLISH, asList(step), asList(tag), asList(mock(PickleLocation.class))); - PickleEvent pickleEvent = new PickleEvent("uri", pickle); - runner.runPickle(pickleEvent); - - assertNull(latestReceivedResult.getError()); - assertTrue(defs.foo); - assertFalse(defs.bar); - } - - public static class Defs { - public boolean foo; - public boolean bar; - - @Given(value = "three (.*) mice") - public void threeDisabledMice(String disability) { - foo = true; - } - - @Given(value = "three blind (.*)") - public void threeBlindAnimals(String animals) { - bar = true; - } - } -} diff --git a/java/src/test/java/cucumber/runtime/java/MethodScannerTest.java b/java/src/test/java/cucumber/runtime/java/MethodScannerTest.java deleted file mode 100644 index 662597c563..0000000000 --- a/java/src/test/java/cucumber/runtime/java/MethodScannerTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package cucumber.runtime.java; - -import io.cucumber.stepexpression.TypeRegistry; -import cucumber.api.java.ObjectFactory; -import cucumber.runtime.CucumberException; -import cucumber.runtime.Glue; -import cucumber.runtime.io.MultiLoader; -import cucumber.runtime.io.ResourceLoader; -import cucumber.runtime.io.ResourceLoaderClassFinder; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; - -import java.net.URI; -import java.util.Collections; -import java.util.Locale; - -import static java.lang.Thread.currentThread; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.*; - -public class MethodScannerTest { - - private ResourceLoaderClassFinder classFinder; - private ObjectFactory factory; - private JavaBackend backend; - - @Before - public void createBackend(){ - ClassLoader classLoader = currentThread().getContextClassLoader(); - ResourceLoader resourceLoader = new MultiLoader(classLoader); - this.classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader); - this.factory = Mockito.mock(ObjectFactory.class); - TypeRegistry typeRegistry = new TypeRegistry(Locale.ENGLISH); - this.backend = new JavaBackend(factory, classFinder, typeRegistry); - } - - @Test - public void loadGlue_registers_the_methods_declaring_class_in_the_object_factory() throws NoSuchMethodException { - MethodScanner methodScanner = new MethodScanner(classFinder); - Glue world = Mockito.mock(Glue.class); - backend.loadGlue(world,Collections.emptyList()); - - // this delegates to methodScanner.scan which we test - methodScanner.scan(backend, BaseStepDefs.class.getMethod("m"), BaseStepDefs.class); - - verify(factory, times(1)).addClass(BaseStepDefs.class); - verifyNoMoreInteractions(factory); - } - - @Test - public void loadGlue_fails_when_class_is_not_method_declaring_class() throws NoSuchMethodException { - try { - backend.loadGlue(null, BaseStepDefs.class.getMethod("m"), Stepdefs2.class); - fail(); - } catch (CucumberException e) { - assertEquals("You're not allowed to extend classes that define Step Definitions or hooks. class cucumber.runtime.java.MethodScannerTest$Stepdefs2 extends class cucumber.runtime.java.MethodScannerTest$BaseStepDefs", e.getMessage()); - } - } - - @Test - public void loadGlue_fails_when_class_is_not_subclass_of_declaring_class() throws NoSuchMethodException { - try { - backend.loadGlue(null, BaseStepDefs.class.getMethod("m"), String.class); - fail(); - } catch (CucumberException e) { - assertEquals("class cucumber.runtime.java.MethodScannerTest$BaseStepDefs isn't assignable from class java.lang.String", e.getMessage()); - } - } - - public static class Stepdefs2 extends BaseStepDefs { - public interface Interface1 { - } - } - - public static class BaseStepDefs { - @cucumber.api.java.Before - public void m() { - } - } -} diff --git a/java/src/test/java/cucumber/runtime/java/StringJoiner.java b/java/src/test/java/cucumber/runtime/java/StringJoiner.java deleted file mode 100644 index 4f20e32e00..0000000000 --- a/java/src/test/java/cucumber/runtime/java/StringJoiner.java +++ /dev/null @@ -1,18 +0,0 @@ -package cucumber.runtime.java; - -public class StringJoiner { - public static String join(String delimiter, Iterable strings){ - //TODO: Java8 replace with StringJoiner. - StringBuilder builder = new StringBuilder(); - boolean first = true; - for (String string : strings) { - if (first) { - first = false; - } else { - builder.append(delimiter); - } - builder.append(string); - } - return builder.toString(); - } -} diff --git a/java/src/test/java/cucumber/runtime/java/incorrectlysubclassedstepdefs/SubclassesStepdefs.java b/java/src/test/java/cucumber/runtime/java/incorrectlysubclassedstepdefs/SubclassesStepdefs.java deleted file mode 100644 index 0a81ebbd42..0000000000 --- a/java/src/test/java/cucumber/runtime/java/incorrectlysubclassedstepdefs/SubclassesStepdefs.java +++ /dev/null @@ -1,6 +0,0 @@ -package cucumber.runtime.java.incorrectlysubclassedstepdefs; - -import cucumber.runtime.java.stepdefs.Stepdefs; - -public class SubclassesStepdefs extends Stepdefs { -} diff --git a/java/src/test/java/cucumber/runtime/java/stepdefs/Stepdefs.java b/java/src/test/java/cucumber/runtime/java/stepdefs/Stepdefs.java deleted file mode 100644 index bbd62867b5..0000000000 --- a/java/src/test/java/cucumber/runtime/java/stepdefs/Stepdefs.java +++ /dev/null @@ -1,11 +0,0 @@ -package cucumber.runtime.java.stepdefs; - -import cucumber.api.java.en.Given; - -public class Stepdefs { - - @Given("test") - public void 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 deleted file mode 100644 index 5cb9d2e364..0000000000 --- a/java/src/test/java/cucumber/runtime/java/test/Authors.java +++ /dev/null @@ -1,75 +0,0 @@ -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/Stepdefs.java b/java/src/test/java/cucumber/runtime/java/test/Stepdefs.java deleted file mode 100644 index a51e4e94ec..0000000000 --- a/java/src/test/java/cucumber/runtime/java/test/Stepdefs.java +++ /dev/null @@ -1,20 +0,0 @@ -package cucumber.runtime.java.test; - -import cucumber.api.java.en.Given; - -import java.util.List; - -public class Stepdefs { - @Given("I have {int} cukes in the belly") - public void I_have_cukes_in_the_belly(int arg1) { - } - - @Given("this data table:") - public void this_data_table(List people) throws Throwable { - } - - public static class Person { - String first; - String last; - } -} diff --git a/java/src/test/java/cucumber/runtime/java/test/TypeRegistryConfiguration.java b/java/src/test/java/cucumber/runtime/java/test/TypeRegistryConfiguration.java deleted file mode 100644 index 783d5b88e8..0000000000 --- a/java/src/test/java/cucumber/runtime/java/test/TypeRegistryConfiguration.java +++ /dev/null @@ -1,64 +0,0 @@ -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/java/io/cucumber/java/AbstractGlueDefinitionTest.java b/java/src/test/java/io/cucumber/java/AbstractGlueDefinitionTest.java new file mode 100644 index 0000000000..fd26f2d99e --- /dev/null +++ b/java/src/test/java/io/cucumber/java/AbstractGlueDefinitionTest.java @@ -0,0 +1,37 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.Lookup; +import org.junit.Test; + +import java.lang.reflect.Method; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.junit.Assert.assertThat; + +public class AbstractGlueDefinitionTest { + private final Lookup lookup = new Lookup() { + + @Override + @SuppressWarnings("unchecked") + public T getInstance(Class glueClass) { + return (T) AbstractGlueDefinitionTest.this; + } + }; + + @Test + public void test() throws NoSuchMethodException { + Method method = AbstractGlueDefinitionTest.class.getMethod("method"); + + AbstractGlueDefinition definition = new AbstractGlueDefinition(method, lookup) { + }; + + assertThat(definition.getLocation(false), is("AbstractGlueDefinitionTest.method()")); + assertThat(definition.getLocation(true), startsWith("io.cucumber.java.AbstractGlueDefinitionTest.method() in ")); + + } + + public void method() { + + } +} \ No newline at end of file diff --git a/java/src/test/java/io/cucumber/java/GlueAdaptorTest.java b/java/src/test/java/io/cucumber/java/GlueAdaptorTest.java new file mode 100644 index 0000000000..cb5872675b --- /dev/null +++ b/java/src/test/java/io/cucumber/java/GlueAdaptorTest.java @@ -0,0 +1,186 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.*; +import io.cucumber.java.en.Given; +import org.hamcrest.CustomTypeSafeMatcher; +import org.hamcrest.Matcher; +import org.junit.Test; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static java.util.Collections.singletonList; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; +import static org.junit.Assert.assertThat; + +public class GlueAdaptorTest { + + private final Lookup lookup = new Lookup() { + + @Override + @SuppressWarnings("unchecked") + public T getInstance(Class glueClass) { + return (T) GlueAdaptorTest.this; + } + }; + + + private final Glue container = new Glue() { + @Override + public void addStepDefinition(StepDefinition stepDefinition) { + GlueAdaptorTest.this.stepDefinitions.add(stepDefinition); + } + + @Override + public void addBeforeHook(HookDefinition beforeHook) { + GlueAdaptorTest.this.beforeHook = beforeHook; + + } + + @Override + public void addAfterHook(HookDefinition afterHook) { + GlueAdaptorTest.this.afterHook = afterHook; + + } + + @Override + public void addBeforeStepHook(HookDefinition beforeStepHook) { + GlueAdaptorTest.this.beforeStepHook = beforeStepHook; + + } + + @Override + public void addAfterStepHook(HookDefinition afterStepHook) { + GlueAdaptorTest.this.afterStepHook = afterStepHook; + + } + + @Override + public void addParameterType(ParameterTypeDefinition parameterTypeDefinition) { + GlueAdaptorTest.this.parameterTypeDefinition = parameterTypeDefinition; + + } + + @Override + public void addDataTableType(DataTableTypeDefinition dataTableTypeDefinition) { + GlueAdaptorTest.this.dataTableTypeDefinition = dataTableTypeDefinition; + + } + + @Override + public void addDefaultParameterTransformer(DefaultParameterTransformerDefinition defaultParameterTransformer) { + GlueAdaptorTest.this.defaultParameterTransformer = defaultParameterTransformer; + + } + + @Override + public void addDefaultDataTableEntryTransformer(DefaultDataTableEntryTransformerDefinition defaultDataTableEntryTransformer) { + GlueAdaptorTest.this.defaultDataTableEntryTransformer = defaultDataTableEntryTransformer; + + } + + @Override + public void addDefaultDataTableCellTransformer(DefaultDataTableCellTransformerDefinition defaultDataTableCellTransformer) { + GlueAdaptorTest.this.defaultDataTableCellTransformer = defaultDataTableCellTransformer; + + } + }; + private final GlueAdaptor adaptor = new GlueAdaptor(lookup, container); + + private final List stepDefinitions = new ArrayList<>(); + private DefaultDataTableCellTransformerDefinition defaultDataTableCellTransformer; + private DefaultDataTableEntryTransformerDefinition defaultDataTableEntryTransformer; + private DefaultParameterTransformerDefinition defaultParameterTransformer; + private DataTableTypeDefinition dataTableTypeDefinition; + private ParameterTypeDefinition parameterTypeDefinition; + private HookDefinition afterStepHook; + private HookDefinition beforeStepHook; + private HookDefinition afterHook; + private HookDefinition beforeHook; + + private final Matcher aStep = new CustomTypeSafeMatcher("a step") { + @Override + protected boolean matchesSafely(StepDefinition item) { + return item.getPattern().equals("a step"); + } + }; + private final Matcher repeated = new CustomTypeSafeMatcher("repeated") { + @Override + protected boolean matchesSafely(StepDefinition item) { + return item.getPattern().equals("repeated"); + } + }; + + @Test + public void creates_all_glue_steps() { + MethodScanner.scan(GlueAdaptorTest.class, adaptor::addDefinition); + + assertThat(stepDefinitions, containsInAnyOrder(aStep, repeated)); + assertThat(defaultDataTableCellTransformer, notNullValue()); + assertThat(defaultDataTableEntryTransformer, notNullValue()); + assertThat(defaultParameterTransformer, notNullValue()); + assertThat(dataTableTypeDefinition, notNullValue()); + assertThat(parameterTypeDefinition.parameterType().getRegexps(), is(singletonList("pattern"))); + assertThat(parameterTypeDefinition.parameterType().getName(), is("name")); + assertThat(afterStepHook, notNullValue()); + assertThat(beforeStepHook, notNullValue()); + assertThat(afterHook, notNullValue()); + assertThat(beforeHook, notNullValue()); + } + + @Given(value = "a step", timeout = 100) + @Given("repeated") + public void step_definition() { + + } + + @DefaultDataTableCellTransformer + public String default_data_table_cell_transformer(String fromValue, Type toValueType) { + return "default_data_table_cell_transformer"; + } + + @DefaultDataTableEntryTransformer + public String default_data_table_entry_transformer(Map fromValue, Type toValueType) { + return "default_data_table_entry_transformer"; + } + + @DefaultParameterTransformer + public String default_parameter_transformer(String fromValue, Type toValueTYpe) { + return "default_parameter_transformer"; + } + + @DataTableType + public String data_table_type(String fromValue) { + return "data_table_type"; + } + + @ParameterType(value = "pattern", name = "name") + public String parameter_type(String fromValue) { + return "parameter_type"; + } + + @AfterStep + public void after_step() { + + } + + @BeforeStep + public void before_step() { + + } + + @After + public void after() { + + } + + @Before + public void before() { + + } + +} \ No newline at end of file diff --git a/java/src/test/java/io/cucumber/java/JavaBackendTest.java b/java/src/test/java/io/cucumber/java/JavaBackendTest.java new file mode 100644 index 0000000000..c3f5835cfe --- /dev/null +++ b/java/src/test/java/io/cucumber/java/JavaBackendTest.java @@ -0,0 +1,82 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.Glue; +import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.backend.StepDefinition; +import io.cucumber.core.io.MultiLoader; +import io.cucumber.core.io.ResourceLoader; +import io.cucumber.java.steps.Steps; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.jupiter.api.function.Executable; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.net.URI; +import java.util.List; + +import static java.lang.Thread.currentThread; +import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class JavaBackendTest { + + @Rule + public MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Captor + public ArgumentCaptor stepDefinition; + + @Mock + private Glue glue; + + @Mock + private ObjectFactory factory; + + private JavaBackend backend; + + @Before + public void createBackend() { + ClassLoader classLoader = currentThread().getContextClassLoader(); + ResourceLoader resourceLoader = new MultiLoader(classLoader); + this.backend = new JavaBackend(factory, factory, resourceLoader); + } + + @Test + public void finds_step_definitions_by_classpath_url() { + backend.loadGlue(glue, asList(URI.create("classpath:io/cucumber/java/steps"))); + backend.buildWorld(); + verify(factory).addClass(Steps.class); + } + + @Test + public void detects_subclassed_glue_and_throws_exception() { + final Executable testMethod = () -> backend.loadGlue(glue, asList(URI.create("classpath:io/cucumber/java/steps"), URI.create("classpath:io/cucumber/java/incorrectlysubclassedsteps"))); + final InvalidMethodException expectedThrown = assertThrows(InvalidMethodException.class, testMethod); + assertThat(expectedThrown.getMessage(), is(equalTo("You're not allowed to extend classes that define Step Definitions or hooks. class io.cucumber.java.incorrectlysubclassedsteps.SubclassesSteps extends class io.cucumber.java.steps.Steps"))); + } + + @Test + public void detects_repeated_annotations() { + backend.loadGlue(glue, asList(URI.create("classpath:io/cucumber/java/repeatable"))); + verify(glue, times(2)).addStepDefinition(stepDefinition.capture()); + + List patterns = stepDefinition.getAllValues() + .stream() + .map(StepDefinition::getPattern) + .collect(toList()); + assertThat(patterns, equalTo(asList("test", "test again"))); + + } + +} diff --git a/java/src/test/java/io/cucumber/java/JavaDataTableTypeDefinitionTest.java b/java/src/test/java/io/cucumber/java/JavaDataTableTypeDefinitionTest.java new file mode 100644 index 0000000000..07914edf9f --- /dev/null +++ b/java/src/test/java/io/cucumber/java/JavaDataTableTypeDefinitionTest.java @@ -0,0 +1,146 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.Lookup; +import io.cucumber.datatable.DataTable; +import org.junit.Test; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.junit.Assert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class JavaDataTableTypeDefinitionTest { + + private final Lookup lookup = new Lookup() { + @Override + @SuppressWarnings("unchecked") + public T getInstance(Class glueClass) { + return (T) JavaDataTableTypeDefinitionTest.this; + } + }; + + private final DataTable dataTable = DataTable.create(asList( + asList("a", "b"), + asList("c", "d") + )); + + @Test + public void can_define_data_table_converter() throws NoSuchMethodException { + Method method = JavaDataTableTypeDefinitionTest.class.getMethod("convert_data_table_to_string", DataTable.class); + JavaDataTableTypeDefinition definition = new JavaDataTableTypeDefinition(method, lookup); + assertThat(definition.dataTableType().transform(dataTable.asLists()), is("convert_data_table_to_string")); + } + + public String convert_data_table_to_string(DataTable table) { + return "convert_data_table_to_string"; + } + + @Test + public void can_define_table_row_transformer() throws NoSuchMethodException { + Method method = JavaDataTableTypeDefinitionTest.class.getMethod("convert_table_row_to_string", List.class); + JavaDataTableTypeDefinition definition = new JavaDataTableTypeDefinition(method, lookup); + assertThat(definition.dataTableType().transform(dataTable.asLists()), + is(asList("convert_table_row_to_string", "convert_table_row_to_string"))); + } + + public String convert_table_row_to_string(List row) { + return "convert_table_row_to_string"; + } + + @Test + public void can_define_table_entry_transformer() throws NoSuchMethodException { + Method method = JavaDataTableTypeDefinitionTest.class.getMethod("converts_table_entry_to_string", Map.class); + JavaDataTableTypeDefinition definition = new JavaDataTableTypeDefinition(method, lookup); + assertThat(definition.dataTableType().transform(dataTable.asLists()), + is(singletonList("converts_table_entry_to_string"))); + } + + public String converts_table_entry_to_string(Map entry) { + return "converts_table_entry_to_string"; + } + + @Test + public void can_define_table_cell_transformer() throws NoSuchMethodException { + Method method = JavaDataTableTypeDefinitionTest.class.getMethod("converts_table_cell_to_string", String.class); + JavaDataTableTypeDefinition definition = new JavaDataTableTypeDefinition(method, lookup); + assertThat(definition.dataTableType().transform(dataTable.asLists()), is(asList( + asList("converts_table_cell_to_string", "converts_table_cell_to_string"), + asList("converts_table_cell_to_string", "converts_table_cell_to_string")) + )); + } + + public String converts_table_cell_to_string(String cell) { + return "converts_table_cell_to_string"; + } + + @Test + public void target_type_must_class_type() throws NoSuchMethodException { + Method method = JavaDataTableTypeDefinitionTest.class.getMethod("converts_datatable_to_optional_string", DataTable.class); + InvalidMethodSignatureException exception = assertThrows(InvalidMethodSignatureException.class, () -> new JavaDataTableTypeDefinition(method, lookup)); + assertThat(exception.getMessage(), startsWith("" + + "A @DataTableType annotated method must have one of these signatures:\n" + + " * public Author author(DataTable table)\n" + + " * public Author author(List row)\n" + + " * public Author author(Map entry)\n" + + " * public Author author(String cell)\n" + + "at io.cucumber.java.JavaDataTableTypeDefinitionTest.converts_datatable_to_optional_string(DataTable) in")); + } + + public Optional converts_datatable_to_optional_string(DataTable table) { + return Optional.of("converts_datatable_to_optional_string"); + } + + @Test + public void target_type_must_not_be_void() throws NoSuchMethodException { + Method method = JavaDataTableTypeDefinitionTest.class.getMethod("converts_data_table_to_void", DataTable.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDataTableTypeDefinition(method, lookup)); + } + + public void converts_data_table_to_void(DataTable table) { + } + + @Test + public void must_have_exactly_one_argument() throws NoSuchMethodException { + Method noArgs = JavaDataTableTypeDefinitionTest.class.getMethod("converts_nothing_to_string"); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDataTableTypeDefinition(noArgs, lookup)); + Method twoArgs = JavaDataTableTypeDefinitionTest.class.getMethod("converts_two_strings_to_string", String.class, String.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDataTableTypeDefinition(twoArgs, lookup)); + } + + public String converts_nothing_to_string() { + return "converts_object_to_string"; + } + + public String converts_two_strings_to_string(String arg1, String arg2) { + return "converts_two_strings_to_string"; + } + + @Test + public void argument_must_match_existing_transformer() throws NoSuchMethodException { + Method method = JavaDataTableTypeDefinitionTest.class.getMethod("converts_object_to_string", Object.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDataTableTypeDefinition(method, lookup)); + } + + public String converts_object_to_string(Object string) { + return "converts_object_to_string"; + } + + @Test + public void table_entry_transformer_must_have_map_of_strings() throws NoSuchMethodException { + Method method = JavaDataTableTypeDefinitionTest.class.getMethod("converts_map_of_objects_to_string", Map.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDataTableTypeDefinition(method, lookup)); + } + + public String converts_map_of_objects_to_string(Map entry) { + return "converts_map_of_objects_to_string"; + } + + +} \ No newline at end of file diff --git a/java/src/test/java/io/cucumber/java/JavaDefaultDataTableCellTransformerDefinitionTest.java b/java/src/test/java/io/cucumber/java/JavaDefaultDataTableCellTransformerDefinitionTest.java new file mode 100644 index 0000000000..5ccadb0b0d --- /dev/null +++ b/java/src/test/java/io/cucumber/java/JavaDefaultDataTableCellTransformerDefinitionTest.java @@ -0,0 +1,102 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.Lookup; +import org.junit.Test; + +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.Map; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.junit.Assert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class JavaDefaultDataTableCellTransformerDefinitionTest { + + private final Lookup lookup = new Lookup() { + + @Override + @SuppressWarnings("unchecked") + public T getInstance(Class glueClass) { + return (T) JavaDefaultDataTableCellTransformerDefinitionTest.this; + } + }; + + @Test + public void can_transform_string_to_type() throws Throwable { + Method method = JavaDefaultDataTableCellTransformerDefinitionTest.class.getMethod("transform_string_to_type", String.class, Type.class); + JavaDefaultDataTableCellTransformerDefinition definition = new JavaDefaultDataTableCellTransformerDefinition(method, lookup); + String transformed = definition.tableCellByTypeTransformer().transform("something", String.class); + assertThat(transformed, is("transform_string_to_type")); + } + + public Object transform_string_to_type(String fromValue, Type toValueType) { + return "transform_string_to_type"; + } + + @Test + public void can_transform_object_to_type() throws Throwable { + Method method = JavaDefaultDataTableCellTransformerDefinitionTest.class.getMethod("transform_object_to_type", Object.class, Type.class); + JavaDefaultDataTableCellTransformerDefinition definition = new JavaDefaultDataTableCellTransformerDefinition(method, lookup); + String transformed = definition.tableCellByTypeTransformer().transform("something", String.class); + assertThat(transformed, is("transform_object_to_type")); + } + + public Object transform_object_to_type(Object fromValue, Type toValueType) { + return "transform_object_to_type"; + } + + @Test + public void must_have_non_void_return() throws Throwable { + Method method = JavaDefaultDataTableCellTransformerDefinitionTest.class.getMethod("transforms_string_to_void", String.class, Type.class); + InvalidMethodSignatureException exception = assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultDataTableCellTransformerDefinition(method, lookup)); + assertThat(exception.getMessage(), startsWith("" + + "A @DefaultDataTableCellTransformer annotated method must have one of these signatures:\n" + + " * public Object defaultDataTableCell(String fromValue, Type toValueType)\n" + + " * public Object defaultDataTableCell(Object fromValue, Type toValueType)\n" + + "at io.cucumber.java.JavaDefaultDataTableCellTransformerDefinitionTest.transforms_string_to_void(String,Type) in" + )); + } + + public void transforms_string_to_void(String fromValue, Type toValueType) { + } + + @Test + public void must_have_two_arguments() throws Throwable { + Method oneArg = JavaDefaultDataTableCellTransformerDefinitionTest.class.getMethod("one_argument", String.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultDataTableCellTransformerDefinition(oneArg, lookup)); + Method threeArg = JavaDefaultDataTableCellTransformerDefinitionTest.class.getMethod("three_arguments", String.class, Type.class, Object.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultDataTableCellTransformerDefinition(threeArg, lookup)); + } + + public Object one_argument(String fromValue) { + return "one_arguments"; + } + + public Object three_arguments(String fromValue, Type toValueType, Object extra) { + return "three_arguments"; + } + + @Test + public void must_have_string_or_object_as_from_value() throws Throwable { + Method threeArg = JavaDefaultDataTableCellTransformerDefinitionTest.class.getMethod("map_as_from_value", Map.class, Type.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultDataTableCellTransformerDefinition(threeArg, lookup)); + } + + + public Object map_as_from_value(Map fromValue, Type toValueType) { + return "map_as_from_value"; + } + + @Test + public void must_have_type_as_to_value_type() throws Throwable { + Method threeArg = JavaDefaultDataTableCellTransformerDefinitionTest.class.getMethod("object_as_to_value_type", String.class, Object.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultDataTableCellTransformerDefinition(threeArg, lookup)); + } + + public Object object_as_to_value_type(String fromValue, Object toValueType) { + return "object_as_to_value_type"; + } + +} \ No newline at end of file diff --git a/java/src/test/java/io/cucumber/java/JavaDefaultDataTableEntryTransformerDefinitionTest.java b/java/src/test/java/io/cucumber/java/JavaDefaultDataTableEntryTransformerDefinitionTest.java new file mode 100644 index 0000000000..066c3fe7ff --- /dev/null +++ b/java/src/test/java/io/cucumber/java/JavaDefaultDataTableEntryTransformerDefinitionTest.java @@ -0,0 +1,147 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.Lookup; +import io.cucumber.datatable.TableCellByTypeTransformer; +import org.junit.Test; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static java.util.Collections.singletonMap; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class JavaDefaultDataTableEntryTransformerDefinitionTest { + + private final Map fromValue = singletonMap("key", "value"); + private final Lookup lookup = new Lookup() { + + @Override + @SuppressWarnings("unchecked") + public T getInstance(Class glueClass) { + return (T) JavaDefaultDataTableEntryTransformerDefinitionTest.this; + } + }; + + private TableCellByTypeTransformer cellTransformer = new TableCellByTypeTransformer() { + @Override + public T transform(String value, Class cellType) { + throw new IllegalStateException(); + } + }; + + @Test + public void transforms_with_correct_method() throws Throwable { + Method method = JavaDefaultDataTableEntryTransformerDefinitionTest.class.getMethod("correct_method", Map.class, Class.class); + JavaDefaultDataTableEntryTransformerDefinition definition = + new JavaDefaultDataTableEntryTransformerDefinition(method, lookup); + + assertThat(definition.tableEntryByTypeTransformer() + .transform(fromValue, String.class, cellTransformer), is("key=value")); + + } + + public T correct_method(Map fromValue, Class toValue) { + return join(fromValue); + } + + + @Test + public void transforms_with_correct_method_with_cell_transformer() throws Throwable { + Method method = JavaDefaultDataTableEntryTransformerDefinitionTest.class.getMethod("correct_method_with_cell_transformer", Map.class, Class.class, TableCellByTypeTransformer.class); + JavaDefaultDataTableEntryTransformerDefinition definition = + new JavaDefaultDataTableEntryTransformerDefinition(method, lookup); + + assertThat(definition.tableEntryByTypeTransformer() + .transform(fromValue, String.class, cellTransformer), is("key=value")); + + } + + + public T correct_method_with_cell_transformer(Map fromValue, Class toValue, TableCellByTypeTransformer cellTransformer) { + return join(fromValue); + } + + @Test + public void method_must_have_2_or_3_arguments() throws Throwable { + Method toFew = JavaDefaultDataTableEntryTransformerDefinitionTest.class.getMethod("one_argument", Map.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultDataTableEntryTransformerDefinition(toFew, lookup)); + Method toMany = JavaDefaultDataTableEntryTransformerDefinitionTest.class.getMethod("four_arguments", Map.class, String.class, String.class, String.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultDataTableEntryTransformerDefinition(toMany, lookup)); + } + + public T one_argument(Map fromValue) { + return null; + } + + + public T four_arguments(Map fromValue, String one, String two, String three) { + return null; + } + + + @Test + public void method_must_have_return_type() throws Throwable { + Method method = JavaDefaultDataTableEntryTransformerDefinitionTest.class.getMethod("void_return_type", Map.class, Class.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultDataTableEntryTransformerDefinition(method, lookup)); + } + + public void void_return_type(Map fromValue, Class toValue) { + } + + + @Test + public void method_must_have_map_as_first_argument() throws Throwable { + Method method = JavaDefaultDataTableEntryTransformerDefinitionTest.class.getMethod("invalid_first_type", String.class, Class.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultDataTableEntryTransformerDefinition(method, lookup)); + Method method2 = JavaDefaultDataTableEntryTransformerDefinitionTest.class.getMethod("invalid_first_type", List.class, Class.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultDataTableEntryTransformerDefinition(method2, lookup)); + Method method3 = JavaDefaultDataTableEntryTransformerDefinitionTest.class.getMethod("invalid_first_type", Map.class, Class.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultDataTableEntryTransformerDefinition(method3, lookup)); + } + + + public T invalid_first_type(String fromValue, Class toValue) { + return null; + } + + public T invalid_first_type(List fromValue, Class toValue) { + return null; + } + + public T invalid_first_type(Map fromValue, Class toValue) { + return null; + } + + @Test + public void method_must_have_class_as_second_argument() throws Throwable { + Method method = JavaDefaultDataTableEntryTransformerDefinitionTest.class.getMethod("invalid_second_type", Map.class, String.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultDataTableEntryTransformerDefinition(method, lookup)); + } + + public T invalid_second_type(Map fromValue, String toValue) { + return null; + } + + + @Test + public void method_must_have_cell_transformer_as_optional_third_argument() throws Throwable { + Method method = JavaDefaultDataTableEntryTransformerDefinitionTest.class.getMethod("invalid_optional_third_type", Map.class, Class.class, String.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultDataTableEntryTransformerDefinition(method, lookup)); + } + + + public T invalid_optional_third_type(Map fromValue, Class toValue, String cellTransformer) { + return null; + } + + private static T join(Map fromValue) { + //noinspection unchecked + return (T) fromValue.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining()); + } + + +} \ No newline at end of file diff --git a/java/src/test/java/io/cucumber/java/JavaDefaultParameterTransformerDefinitionTest.java b/java/src/test/java/io/cucumber/java/JavaDefaultParameterTransformerDefinitionTest.java new file mode 100644 index 0000000000..0e029df54c --- /dev/null +++ b/java/src/test/java/io/cucumber/java/JavaDefaultParameterTransformerDefinitionTest.java @@ -0,0 +1,102 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.Lookup; +import org.junit.Test; + +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.Map; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.junit.Assert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class JavaDefaultParameterTransformerDefinitionTest { + + private final Lookup lookup = new Lookup() { + + @Override + @SuppressWarnings("unchecked") + public T getInstance(Class glueClass) { + return (T) JavaDefaultParameterTransformerDefinitionTest.this; + } + }; + + @Test + public void can_transform_string_to_type() throws Throwable { + Method method = JavaDefaultParameterTransformerDefinitionTest.class.getMethod("transform_string_to_type", String.class, Type.class); + JavaDefaultParameterTransformerDefinition definition = new JavaDefaultParameterTransformerDefinition(method, lookup); + Object transformed = definition.parameterByTypeTransformer().transform("something", String.class); + assertThat(transformed, is("transform_string_to_type")); + } + + public Object transform_string_to_type(String fromValue, Type toValueType) { + return "transform_string_to_type"; + } + + @Test + public void can_transform_object_to_type() throws Throwable { + Method method = JavaDefaultParameterTransformerDefinitionTest.class.getMethod("transform_object_to_type", Object.class, Type.class); + JavaDefaultParameterTransformerDefinition definition = new JavaDefaultParameterTransformerDefinition(method, lookup); + String transformed = (String) definition.parameterByTypeTransformer().transform("something", String.class); + assertThat(transformed, is("transform_object_to_type")); + } + + public Object transform_object_to_type(Object fromValue, Type toValueType) { + return "transform_object_to_type"; + } + + @Test + public void must_have_non_void_return() throws Throwable { + Method method = JavaDefaultParameterTransformerDefinitionTest.class.getMethod("transforms_string_to_void", String.class, Type.class); + InvalidMethodSignatureException exception = assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultParameterTransformerDefinition(method, lookup)); + assertThat(exception.getMessage(), startsWith("" + + "A @DefaultParameterTransformer annotated method must have one of these signatures:\n" + + " * public Object defaultDataTableEntry(String fromValue, Type toValueType)\n" + + " * public Object defaultDataTableEntry(Object fromValue, Type toValueType)\n" + + "at io.cucumber.java.JavaDefaultParameterTransformerDefinitionTest.transforms_string_to_void(String,Type) in" + )); + } + + public void transforms_string_to_void(String fromValue, Type toValueType) { + } + + @Test + public void must_have_two_arguments() throws Throwable { + Method oneArg = JavaDefaultParameterTransformerDefinitionTest.class.getMethod("one_argument", String.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultParameterTransformerDefinition(oneArg, lookup)); + Method threeArg = JavaDefaultParameterTransformerDefinitionTest.class.getMethod("three_arguments", String.class, Type.class, Object.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultParameterTransformerDefinition(threeArg, lookup)); + } + + public Object one_argument(String fromValue) { + return "one_arguments"; + } + + public Object three_arguments(String fromValue, Type toValueType, Object extra) { + return "three_arguments"; + } + + @Test + public void must_have_string_or_object_as_from_value() throws Throwable { + Method threeArg = JavaDefaultParameterTransformerDefinitionTest.class.getMethod("map_as_from_value", Map.class, Type.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultParameterTransformerDefinition(threeArg, lookup)); + } + + + public Object map_as_from_value(Map fromValue, Type toValueType) { + return "map_as_from_value"; + } + + @Test + public void must_have_type_as_to_value_type() throws Throwable { + Method threeArg = JavaDefaultParameterTransformerDefinitionTest.class.getMethod("object_as_to_value_type", String.class, Object.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultParameterTransformerDefinition(threeArg, lookup)); + } + + public Object object_as_to_value_type(String fromValue, Object toValueType) { + return "object_as_to_value_type"; + } + +} \ No newline at end of file diff --git a/java/src/test/java/io/cucumber/java/JavaHookDefinitionTest.java b/java/src/test/java/io/cucumber/java/JavaHookDefinitionTest.java new file mode 100644 index 0000000000..e3d4cdf546 --- /dev/null +++ b/java/src/test/java/io/cucumber/java/JavaHookDefinitionTest.java @@ -0,0 +1,114 @@ +package io.cucumber.java; + +import io.cucumber.core.api.Scenario; +import io.cucumber.core.backend.Lookup; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.lang.reflect.Method; +import java.util.List; + +import static org.hamcrest.CoreMatchers.startsWith; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.quality.Strictness.STRICT_STUBS; + +public class JavaHookDefinitionTest { + + @Rule + public MockitoRule mockitoRule = MockitoJUnit.rule().strictness(STRICT_STUBS); + + private final Lookup lookup = new Lookup() { + + @Override + @SuppressWarnings("unchecked") + public T getInstance(Class glueClass) { + return (T) JavaHookDefinitionTest.this; + } + }; + + @Mock + private Scenario scenario; + + private boolean invoked = false; + + @Test + public void can_create_with_no_argument() throws Throwable { + Method method = JavaHookDefinitionTest.class.getMethod("no_arguments"); + JavaHookDefinition definition = new JavaHookDefinition(method, "", 0, 0, lookup); + definition.execute(scenario); + assertTrue(invoked); + } + + + @Before + public void no_arguments() { + invoked = true; + } + + @Test + public void can_create_with_single_scenario_argument() throws Throwable { + Method method = JavaHookDefinitionTest.class.getMethod("single_argument", Scenario.class); + JavaHookDefinition definition = new JavaHookDefinition(method, "", 0, 0, lookup); + definition.execute(scenario); + assertTrue(invoked); + } + + @Before + public void single_argument(Scenario scenario) { + invoked = true; + } + + @Test + public void fails_if_hook_argument_is_not_scenario_result() throws NoSuchMethodException { + Method method = JavaHookDefinitionTest.class.getMethod("invalid_parameter", String.class); + InvalidMethodSignatureException exception = assertThrows( + InvalidMethodSignatureException.class, + () -> new JavaHookDefinition(method, "", 0, 0, lookup) + ); + assertThat(exception.getMessage(), startsWith("" + + "A method annotated with Before, After, BeforeStep or AfterStep must have one of these signatures:\n" + + " * public void before_or_after(Scenario scenario)\n" + + " * public void before_or_after()\n" + + "at io.cucumber.java.JavaHookDefinitionTest.invalid_parameter(String) in file:")); + } + + + public void invalid_parameter(String badType) { + + } + + @Test + public void fails_if_generic_hook_argument_is_not_scenario_result() throws NoSuchMethodException { + Method method = JavaHookDefinitionTest.class.getMethod("invalid_generic_parameter", List.class); + assertThrows( + InvalidMethodSignatureException.class, + () -> new JavaHookDefinition(method, "", 0, 0, lookup) + ); + } + + + public void invalid_generic_parameter(List badType) { + + } + + @Test + public void fails_if_too_many_arguments() throws NoSuchMethodException { + Method method = JavaHookDefinitionTest.class.getMethod("too_many_parameters", Scenario.class, String.class); + assertThrows( + InvalidMethodSignatureException.class, + () -> new JavaHookDefinition(method, "", 0, 0, lookup) + ); + } + + + public void too_many_parameters(Scenario arg1, String arg2) { + + } + + +} diff --git a/java/src/test/java/io/cucumber/java/JavaParameterTypeDefinitionTest.java b/java/src/test/java/io/cucumber/java/JavaParameterTypeDefinitionTest.java new file mode 100644 index 0000000000..99d14d85b2 --- /dev/null +++ b/java/src/test/java/io/cucumber/java/JavaParameterTypeDefinitionTest.java @@ -0,0 +1,133 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.Lookup; +import io.cucumber.cucumberexpressions.Argument; +import io.cucumber.cucumberexpressions.CucumberExpression; +import io.cucumber.cucumberexpressions.CucumberExpressionException; +import io.cucumber.cucumberexpressions.ParameterTypeRegistry; +import org.junit.Test; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Locale; +import java.util.Optional; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class JavaParameterTypeDefinitionTest { + + private final Lookup lookup = new Lookup() { + @Override + @SuppressWarnings("unchecked") + public T getInstance(Class glueClass) { + return (T) JavaParameterTypeDefinitionTest.this; + } + }; + + private final ParameterTypeRegistry registry = new ParameterTypeRegistry(Locale.ENGLISH); + + @Test + public void can_define_parameter_type_converters_with_one_capture_group() throws NoSuchMethodException { + Method method = JavaParameterTypeDefinitionTest.class.getMethod("convert_one_capture_group_to_string", String.class); + JavaParameterTypeDefinition definition = new JavaParameterTypeDefinition("", "(.*)", method, false, false, lookup); + registry.defineParameterType(definition.parameterType()); + CucumberExpression cucumberExpression = new CucumberExpression("{convert_one_capture_group_to_string}", registry); + List> test = cucumberExpression.match("test"); + assertThat(test.get(0).getValue(), equalTo("convert_one_capture_group_to_string")); + } + + public String convert_one_capture_group_to_string(String all) { + return "convert_one_capture_group_to_string"; + } + + @Test + public void can_define_parameter_type_converters_with_two_capture_groups() throws NoSuchMethodException { + Method method = JavaParameterTypeDefinitionTest.class.getMethod("convert_two_capture_group_to_string", String.class, String.class); + JavaParameterTypeDefinition definition = new JavaParameterTypeDefinition("", "([^ ]*) ([^ ]*)", method, false, false, lookup); + registry.defineParameterType(definition.parameterType()); + CucumberExpression cucumberExpression = new CucumberExpression("{convert_two_capture_group_to_string}", registry); + List> test = cucumberExpression.match("test test"); + assertThat(test.get(0).getValue(), equalTo("convert_two_capture_group_to_string")); + } + + public String convert_two_capture_group_to_string(String captureGroup1, String captureGroup2) { + return "convert_two_capture_group_to_string"; + } + + @Test + public void can_define_parameter_type_converters_with_var_args() throws NoSuchMethodException { + Method method = JavaParameterTypeDefinitionTest.class.getMethod("convert_varargs_capture_group_to_string", String[].class); + JavaParameterTypeDefinition definition = new JavaParameterTypeDefinition("", "([^ ]*) ([^ ]*)", method, false, false, lookup); + registry.defineParameterType(definition.parameterType()); + CucumberExpression cucumberExpression = new CucumberExpression("{convert_varargs_capture_group_to_string}", registry); + List> test = cucumberExpression.match("test test"); + assertThat(test.get(0).getValue(), equalTo("convert_varargs_capture_group_to_string")); + } + + public String convert_varargs_capture_group_to_string(String... captureGroups) { + return "convert_varargs_capture_group_to_string"; + } + + @Test + public void arguments_must_match_captured_groups() throws NoSuchMethodException { + Method method = JavaParameterTypeDefinitionTest.class.getMethod("convert_two_capture_group_to_string", String.class, String.class); + JavaParameterTypeDefinition definition = new JavaParameterTypeDefinition("", ".*", method, false, false, lookup); + registry.defineParameterType(definition.parameterType()); + CucumberExpression cucumberExpression = new CucumberExpression("{convert_two_capture_group_to_string}", registry); + List> test = cucumberExpression.match("test"); + assertThrows(CucumberExpressionException.class, () -> test.get(0).getValue()); + } + + + @Test + public void converter_must_have_return_type() throws NoSuchMethodException { + Method method = JavaParameterTypeDefinitionTest.class.getMethod("convert_capture_group_to_void", String.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaParameterTypeDefinition("", "(.*)", method, false, false, lookup)); + } + + public void convert_capture_group_to_void(String all) { + } + + @Test + public void converter_must_have_non_generic_return_type() throws NoSuchMethodException { + Method method = JavaParameterTypeDefinitionTest.class.getMethod("convert_capture_group_to_optional_string", String.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaParameterTypeDefinition("", "(.*)", method, false, false, lookup)); + } + + public Optional convert_capture_group_to_optional_string(String all) { + return Optional.of("convert_capture_group_to_optional_string"); + } + + @Test + public void converter_must_have_at_least_one_argument() throws NoSuchMethodException { + Method method = JavaParameterTypeDefinitionTest.class.getMethod("convert_nothing_to_string"); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaParameterTypeDefinition("", "(.*)", method, false, false, lookup)); + } + + public String convert_nothing_to_string() { + return "convert_nothing_to_string"; + } + + @Test + public void converter_must_have_string_arguments() throws NoSuchMethodException { + Method method = JavaParameterTypeDefinitionTest.class.getMethod("converts_object_to_string", Object.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaParameterTypeDefinition("", "(.*)", method, false, false, lookup)); + } + + public String converts_object_to_string(Object other) { + return "converts_object_to_string"; + } + + @Test + public void converter_must_have_all_string_arguments() throws NoSuchMethodException { + Method method = JavaParameterTypeDefinitionTest.class.getMethod("converts_objects_to_string", String.class, Object.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaParameterTypeDefinition("", "(.*)", method, false, false, lookup)); + } + + public String converts_objects_to_string(String all, Object other) { + return "converts_object_to_string"; + } + +} \ No newline at end of file diff --git a/java/src/test/java/cucumber/runtime/java/JavaSnippetTest.java b/java/src/test/java/io/cucumber/java/JavaSnippetTest.java similarity index 86% rename from java/src/test/java/cucumber/runtime/java/JavaSnippetTest.java rename to java/src/test/java/io/cucumber/java/JavaSnippetTest.java index e1cb252981..91f43a3f67 100644 --- a/java/src/test/java/cucumber/runtime/java/JavaSnippetTest.java +++ b/java/src/test/java/io/cucumber/java/JavaSnippetTest.java @@ -1,8 +1,7 @@ -package cucumber.runtime.java; +package io.cucumber.java; -import cucumber.runtime.snippets.FunctionNameGenerator; -import cucumber.runtime.snippets.SnippetGenerator; -import cucumber.runtime.snippets.UnderscoreConcatenator; +import io.cucumber.core.snippets.SnippetType; +import io.cucumber.core.snippets.SnippetGenerator; import gherkin.pickles.Argument; import gherkin.pickles.PickleCell; import gherkin.pickles.PickleLocation; @@ -28,7 +27,7 @@ public class JavaSnippetTest { private static final String GIVEN_KEYWORD = "Given"; - private final FunctionNameGenerator functionNameGenerator = new FunctionNameGenerator(new UnderscoreConcatenator()); + private final SnippetType snippetType = SnippetType.UNDERSCORE; @Test public void generatesPlainSnippet() { @@ -36,7 +35,7 @@ public void generatesPlainSnippet() { "@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 cucumber.api.PendingException();\n" + + " throw new io.cucumber.java.PendingException();\n" + "}\n"; assertEquals(expected, snippetFor("I have 4 cukes in my \"big\" belly")); } @@ -59,7 +58,7 @@ public Size transform(String... strings) { "@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 cucumber.api.PendingException();\n" + + " throw new io.cucumber.java.PendingException();\n" + "}\n"; assertEquals(expected, snippetFor("I have 4.2 cukes in my large belly", customParameterType)); } @@ -82,9 +81,9 @@ public List transform(String... strings) { String expected = "" + "@Given(\"I have {sizes} bellies\")\n" + - "public void i_have_bellies(java.util.List sizes) {\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 cucumber.api.PendingException();\n" + + " throw new io.cucumber.java.PendingException();\n" + "}\n"; assertEquals(expected, snippetFor("I have large and small bellies", customParameterType)); } @@ -95,7 +94,7 @@ public void generatesCopyPasteReadyStepSnippetForNumberParameters() { "@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 cucumber.api.PendingException();\n" + + " throw new io.cucumber.java.PendingException();\n" + "}\n"; assertEquals(expected, snippetFor("before 5 after")); } @@ -106,7 +105,7 @@ public void generatesCopyPasteReadySnippetWhenStepHasIllegalJavaIdentifierChars( "@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 cucumber.api.PendingException();\n" + + " throw new io.cucumber.java.PendingException();\n" + "}\n"; assertEquals(expected, snippetFor("I have 4 cukes in: my \"big\" red-belly!")); } @@ -117,7 +116,7 @@ public void generatesCopyPasteReadySnippetWhenStepHasIntegersInsideStringParamet "@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 cucumber.api.PendingException();\n" + + " throw new io.cucumber.java.PendingException();\n" + "}\n"; assertEquals(expected, snippetFor("the DI system receives a message saying \"{ dataIngestion: { feeds: [ feed: { merchantId: 666, feedId: 1, feedFileLocation: feed.csv } ] }\"")); } @@ -128,7 +127,7 @@ public void generatesSnippetWithDollarSigns() { "@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 cucumber.api.PendingException();\n" + + " throw new io.cucumber.java.PendingException();\n" + "}\n"; assertEquals(expected, snippetFor("I have $5")); } @@ -139,7 +138,7 @@ public void generatesSnippetWithQuestionMarks() { "@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 cucumber.api.PendingException();\n" + + " throw new io.cucumber.java.PendingException();\n" + "}\n"; assertEquals(expected, snippetFor("is there an error?:")); } @@ -150,7 +149,7 @@ public void generatesSnippetWithLotsOfNonIdentifierCharacters() { "@Given(\"\\\\([a-z]*)?.+\")\n" + "public void a_z() {\n" + " // Write code here that turns the phrase above into concrete actions\n" + - " throw new cucumber.api.PendingException();\n" + + " throw new io.cucumber.java.PendingException();\n" + "}\n"; assertEquals(expected, snippetFor("([a-z]*)?.+")); } @@ -161,7 +160,7 @@ public void generatesSnippetWithParentheses() { "@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 cucumber.api.PendingException();\n" + + " throw new io.cucumber.java.PendingException();\n" + "}\n"; assertEquals(expected, snippetFor("I have 5 cukes (maybe more)")); } @@ -172,7 +171,7 @@ public void generatesSnippetWithBrackets() { "@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 cucumber.api.PendingException();\n" + + " throw new io.cucumber.java.PendingException();\n" + "}\n"; assertEquals(expected, snippetFor("I have 5 cukes [maybe more]")); } @@ -183,7 +182,7 @@ public void generatesSnippetWithDocString() { "@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 cucumber.api.PendingException();\n" + + " throw new io.cucumber.java.PendingException();\n" + "}\n"; assertEquals(expected, snippetForDocString("I have:", new PickleString(null, "hello"))); } @@ -207,13 +206,13 @@ public String transform(String... strings) { "@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 cucumber.api.PendingException();\n" + + " throw new io.cucumber.java.PendingException();\n" + "}\n" + "\n" + "@Given(\"I have a {string}:\")\n" + "public void i_have_a(String string, String docString) {\n" + " // Write code here that turns the phrase above into concrete actions\n" + - " throw new cucumber.api.PendingException();\n" + + " throw new io.cucumber.java.PendingException();\n" + "}\n"; assertEquals(expected, snippetForDocString("I have a \"Documentation String\":", new PickleString(null, "hello"), customParameterType)); } @@ -242,7 +241,7 @@ public void generatesSnippetWithDataTable() { " // Double, Byte, Short, Long, BigInteger or BigDecimal.\n" + " //\n" + " // For other transformations you can register a DataTableType.\n" + - " throw new cucumber.api.PendingException();\n" + + " throw new io.cucumber.java.PendingException();\n" + "}\n"; PickleTable dataTable = new PickleTable(asList(new PickleRow(asList(new PickleCell(null, "col1"))))); assertEquals(expected, snippetForDataTable("I have:", dataTable)); @@ -274,7 +273,7 @@ public String transform(String... strings) { " // Double, Byte, Short, Long, BigInteger or BigDecimal.\n" + " //\n" + " // For other transformations you can register a DataTableType.\n" + - " throw new cucumber.api.PendingException();\n" + + " throw new io.cucumber.java.PendingException();\n" + "}\n" + "\n" + "@Given(\"I have in table {string}:\")\n" + @@ -286,7 +285,7 @@ public String transform(String... strings) { " // Double, Byte, Short, Long, BigInteger or BigDecimal.\n" + " //\n" + " // For other transformations you can register a DataTableType.\n" + - " throw new cucumber.api.PendingException();\n" + + " throw new io.cucumber.java.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)); @@ -298,7 +297,7 @@ public void generateSnippetWithOutlineParam() { "@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 cucumber.api.PendingException();\n" + + " throw new io.cucumber.java.PendingException();\n" + "}\n"; assertEquals(expected, snippetFor("Then it responds ")); @@ -306,9 +305,8 @@ public void generateSnippetWithOutlineParam() { private String snippetFor(String name) { PickleStep step = new PickleStep(name, Collections.emptyList(), Collections.emptyList()); - List snippet = new SnippetGenerator(new JavaSnippet(), new ParameterTypeRegistry(Locale.ENGLISH)) - .getSnippet(step, GIVEN_KEYWORD, functionNameGenerator); - return StringJoiner.join("\n", snippet); + List snippet = new SnippetGenerator(new JavaSnippet(), new ParameterTypeRegistry(Locale.ENGLISH)).getSnippet(step, GIVEN_KEYWORD, snippetType); + return String.join("\n", snippet); } @@ -316,14 +314,14 @@ 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); - List snippet = new SnippetGenerator(new JavaSnippet(), parameterTypeRegistry).getSnippet(step, GIVEN_KEYWORD, functionNameGenerator); - return StringJoiner.join("\n", snippet); + List snippet = new SnippetGenerator(new JavaSnippet(), parameterTypeRegistry).getSnippet(step, GIVEN_KEYWORD, snippetType); + return String.join("\n", snippet); } private String snippetForDocString(String name, PickleString docString) { PickleStep step = new PickleStep(name, asList((Argument) docString), Collections.emptyList()); - List snippet = new SnippetGenerator(new JavaSnippet(), new ParameterTypeRegistry(Locale.ENGLISH)).getSnippet(step, GIVEN_KEYWORD, functionNameGenerator); - return StringJoiner.join("\n", snippet); + List snippet = new SnippetGenerator(new JavaSnippet(), new ParameterTypeRegistry(Locale.ENGLISH)).getSnippet(step, GIVEN_KEYWORD, snippetType); + return String.join("\n", snippet); } @@ -331,15 +329,15 @@ private String snippetForDocString(String name, PickleString docString, Paramete PickleStep step = new PickleStep(name, asList((Argument) docString), Collections.emptyList()); ParameterTypeRegistry parameterTypeRegistry = new ParameterTypeRegistry(Locale.ENGLISH); parameterTypeRegistry.defineParameterType(parameterType); - List snippet = new SnippetGenerator(new JavaSnippet(), parameterTypeRegistry).getSnippet(step, GIVEN_KEYWORD, functionNameGenerator); - return StringJoiner.join("\n", snippet); + List snippet = new SnippetGenerator(new JavaSnippet(), parameterTypeRegistry).getSnippet(step, GIVEN_KEYWORD, snippetType); + return String.join("\n", snippet); } private String snippetForDataTable(String name, PickleTable dataTable) { PickleStep step = new PickleStep(name, asList((Argument) dataTable), Collections.emptyList()); - List snippet = new SnippetGenerator(new JavaSnippet(), new ParameterTypeRegistry(Locale.ENGLISH)).getSnippet(step, GIVEN_KEYWORD, functionNameGenerator); - return StringJoiner.join("\n", snippet); + List snippet = new SnippetGenerator(new JavaSnippet(), new ParameterTypeRegistry(Locale.ENGLISH)).getSnippet(step, GIVEN_KEYWORD, snippetType); + return String.join("\n", snippet); } @@ -347,8 +345,8 @@ private String snippetForDataTable(String name, PickleTable dataTable, Parameter PickleStep step = new PickleStep(name, asList((Argument) dataTable), Collections.emptyList()); ParameterTypeRegistry parameterTypeRegistry = new ParameterTypeRegistry(Locale.ENGLISH); parameterTypeRegistry.defineParameterType(parameterType); - List snippet = new SnippetGenerator(new JavaSnippet(), parameterTypeRegistry).getSnippet(step, GIVEN_KEYWORD, functionNameGenerator); - return StringJoiner.join("\n", snippet); + List snippet = new SnippetGenerator(new JavaSnippet(), parameterTypeRegistry).getSnippet(step, GIVEN_KEYWORD, snippetType); + return String.join("\n", snippet); } private static class Size { diff --git a/java/src/test/java/io/cucumber/java/JavaStepDefinitionTest.java b/java/src/test/java/io/cucumber/java/JavaStepDefinitionTest.java new file mode 100644 index 0000000000..67f359eeae --- /dev/null +++ b/java/src/test/java/io/cucumber/java/JavaStepDefinitionTest.java @@ -0,0 +1,55 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.Lookup; +import org.junit.Test; + +import java.lang.reflect.Method; +import java.util.Optional; + +import static java.util.Arrays.stream; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class JavaStepDefinitionTest { + + private final Lookup lookup = new Lookup() { + + @Override + @SuppressWarnings("unchecked") + public T getInstance(Class glueClass) { + return (T) JavaStepDefinitionTest.this; + } + }; + + private String argument; + + @Test + public void can_define_step() throws Throwable { + Method method = JavaStepDefinitionTest.class.getMethod("one_string_argument", String.class); + JavaStepDefinition definition = new JavaStepDefinition(method, "three (.*) mice", 0, lookup); + definition.execute(new Object[]{"one_string_argument"}); + assertThat(argument, is("one_string_argument")); + } + + public void one_string_argument(String argument) { + this.argument = argument; + } + + @Test + public void can_provide_location_of_step() throws Throwable { + Method method = JavaStepDefinitionTest.class.getMethod("method_throws"); + JavaStepDefinition definition = new JavaStepDefinition(method, "three (.*) mice", 0, lookup); + PendingException exception = assertThrows(PendingException.class, () -> definition.execute(new Object[0])); + Optional match = stream(exception.getStackTrace()).filter(definition::isDefinedAt).findFirst(); + StackTraceElement stackTraceElement = match.get(); + assertThat(stackTraceElement.getMethodName(), is("method_throws")); + assertThat(stackTraceElement.getClassName(), is(JavaStepDefinitionTest.class.getName())); + } + + public void method_throws() { + throw new PendingException(); + } + + +} diff --git a/java/src/test/java/io/cucumber/java/JavaStepDefinitionTransposeTest.java b/java/src/test/java/io/cucumber/java/JavaStepDefinitionTransposeTest.java new file mode 100755 index 0000000000..1dc368e67d --- /dev/null +++ b/java/src/test/java/io/cucumber/java/JavaStepDefinitionTransposeTest.java @@ -0,0 +1,45 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.Lookup; +import io.cucumber.core.backend.StepDefinition; +import org.junit.Test; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class JavaStepDefinitionTransposeTest { + + public static class StepDefs { + + public void mapOfDoubleToDouble(Map mapOfDoubleToDouble) { + + } + + public void transposedMapOfDoubleToListOfDouble(@Transpose Map> mapOfDoubleToListOfDouble) { + } + } + + @Test + public void transforms_to_map_of_double_to_double() throws Throwable { + Method m = StepDefs.class.getMethod("mapOfDoubleToDouble", Map.class); + assertFalse(isTransposed(m)); + } + + @Test + public void transforms_transposed_to_map_of_double_to_double() throws Throwable { + Method m = StepDefs.class.getMethod("transposedMapOfDoubleToListOfDouble", Map.class); + assertTrue(isTransposed(m)); + } + + private boolean isTransposed(Method method) { + StepDefs stepDefs = new StepDefs(); + Lookup lookup = new SingletonFactory(stepDefs); + StepDefinition stepDefinition = new JavaStepDefinition(method, "some text", 0, lookup); + + return stepDefinition.parameterInfos().get(0).isTransposed(); + } +} diff --git a/java/src/test/java/io/cucumber/java/MethodScannerTest.java b/java/src/test/java/io/cucumber/java/MethodScannerTest.java new file mode 100644 index 0000000000..17ce95c497 --- /dev/null +++ b/java/src/test/java/io/cucumber/java/MethodScannerTest.java @@ -0,0 +1,63 @@ +package io.cucumber.java; + +import org.junit.Before; +import org.junit.Test; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.AbstractMap.SimpleEntry; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.collection.IsEmptyCollection.empty; +import static org.hamcrest.collection.IsIterableContainingInOrder.contains; +import static org.junit.Assert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class MethodScannerTest { + + private final List> scanResult = new ArrayList<>(); + private BiConsumer backend = (method, annotation) -> + scanResult.add(new SimpleEntry<>(method, annotation)); + + @Before + public void createBackend() { + + } + + @Test + public void scan_finds_annotated_methods() throws NoSuchMethodException { + Method method = BaseStepDefs.class.getMethod("m"); + MethodScanner.scan(BaseStepDefs.class, backend); + assertThat(scanResult, contains(new SimpleEntry<>(method, method.getAnnotations()[0]))); + } + + @Test + public void scan_ignores_object() throws NoSuchMethodException { + MethodScanner.scan(Object.class, backend); + assertThat(scanResult, empty()); + } + + @Test + public void loadGlue_fails_when_class_is_not_method_declaring_class() { + InvalidMethodException exception = assertThrows(InvalidMethodException.class, () -> MethodScanner.scan(Stepdefs2.class, backend)); + assertThat(exception.getMessage(), is( + "You're not allowed to extend classes that define Step Definitions or hooks. " + + "class io.cucumber.java.MethodScannerTest$Stepdefs2 extends class io.cucumber.java.MethodScannerTest$BaseStepDefs" + )); + } + + public static class Stepdefs2 extends BaseStepDefs { + public interface Interface1 { + } + } + + public static class BaseStepDefs { + @io.cucumber.java.Before + public void m() { + } + } +} diff --git a/java/src/test/java/cucumber/runtime/java/SingletonFactory.java b/java/src/test/java/io/cucumber/java/SingletonFactory.java similarity index 90% rename from java/src/test/java/cucumber/runtime/java/SingletonFactory.java rename to java/src/test/java/io/cucumber/java/SingletonFactory.java index f07273fdf2..b1792a8897 100644 --- a/java/src/test/java/cucumber/runtime/java/SingletonFactory.java +++ b/java/src/test/java/io/cucumber/java/SingletonFactory.java @@ -1,6 +1,6 @@ -package cucumber.runtime.java; +package io.cucumber.java; -import cucumber.api.java.ObjectFactory; +import io.cucumber.core.backend.ObjectFactory; class SingletonFactory implements ObjectFactory { private Object singleton; diff --git a/java/src/test/java/io/cucumber/java/annotation/DataTableSteps.java b/java/src/test/java/io/cucumber/java/annotation/DataTableSteps.java new file mode 100644 index 0000000000..c8bdaf5498 --- /dev/null +++ b/java/src/test/java/io/cucumber/java/annotation/DataTableSteps.java @@ -0,0 +1,132 @@ +package io.cucumber.java.annotation; + +import io.cucumber.datatable.DataTable; +import io.cucumber.java.DataTableType; +import io.cucumber.java.Transpose; +import io.cucumber.java.en.Given; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class DataTableSteps { + + private final Author expectedAuthor = new Author("Annie M. G.", "Schmidt", "1911-03-20"); + private final Person expectedPerson = new Person("Astrid", "Lindgren"); + + @DataTableType + public Author authorEntryTransformer(Map entry) { + return new DataTableSteps.Author( + entry.get("firstName"), + entry.get("lastName"), + entry.get("birthDate")); + } + + @DataTableType + public Author singleAuthorTransformer(DataTable table) { + return authorEntryTransformer(table.asMaps().get(0)); + } + + @Given("a list of authors in a table") + public void aListOfAuthorsInATable(List authors) { + assertTrue(authors.contains(expectedAuthor)); + } + + @Given("a list of authors in a transposed table") + public void aListOfAuthorsInATransposedTable(@Transpose List authors) { + assertTrue(authors.contains(expectedAuthor)); + } + + @Given("a single author in a table") + public void aSingleAuthorInATable(Author author) { + assertEquals(expectedAuthor, author); + } + + @Given("a single author in a transposed table") + public void aSingleAuthorInATransposedTable(@Transpose Author author) { + assertEquals(expectedAuthor, author); + } + + public static class Author { + final String firstName; + final String lastName; + final String birthDate; + + Author(String firstName, String lastName, String birthDate) { + this.firstName = firstName; + this.lastName = lastName; + this.birthDate = birthDate; + } + + @Override + public String toString() { + return "Author{" + + "firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", birthDate='" + birthDate + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Author author = (Author) o; + + if (!firstName.equals(author.firstName)) return false; + if (!lastName.equals(author.lastName)) return false; + return birthDate.equals(author.birthDate); + } + + @Override + public int hashCode() { + int result = firstName.hashCode(); + result = 31 * result + lastName.hashCode(); + result = 31 * result + birthDate.hashCode(); + return result; + } + } + + @Given("a list of people in a table") + public void this_table_of_authors(List persons) { + assertTrue(persons.contains(expectedPerson)); + } + + @DataTableType + public DataTableSteps.Person transform(Map tableEntry) { + return new Person(tableEntry.get("first"), tableEntry.get("last")); + } + + public static class Person { + private final String first; + private final String last; + + public Person(String first, String last) { + this.first = first; + this.last = last; + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Person person = (Person) o; + return first.equals(person.first) && + last.equals(person.last); + } + + @Override + public int hashCode() { + return Objects.hash(first, last); + } + + + } + + +} diff --git a/java/src/test/java/io/cucumber/java/annotation/FrenchSteps.java b/java/src/test/java/io/cucumber/java/annotation/FrenchSteps.java new file mode 100644 index 0000000000..5393743503 --- /dev/null +++ b/java/src/test/java/io/cucumber/java/annotation/FrenchSteps.java @@ -0,0 +1,21 @@ +package io.cucumber.java.annotation; + +import io.cucumber.java.fr.Étantdonné; + +import java.math.BigDecimal; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +public class FrenchSteps { + + @Étantdonné("j'ai {bigdecimal} concombres fractionnaires") + public void jAiConcombresFractionnaires(BigDecimal arg0) { + assertThat(arg0, is(new BigDecimal("5.5"))); + } + + @Étantdonné("j'ai {int} concombres") + public void jAiConcombres(int arg0) { + assertThat(arg0, is(5)); + } +} diff --git a/java/src/test/java/io/cucumber/java/annotation/ParameterTypeSteps.java b/java/src/test/java/io/cucumber/java/annotation/ParameterTypeSteps.java new file mode 100644 index 0000000000..a650b3a7bf --- /dev/null +++ b/java/src/test/java/io/cucumber/java/annotation/ParameterTypeSteps.java @@ -0,0 +1,28 @@ +package io.cucumber.java.annotation; + +import io.cucumber.java.ParameterType; +import io.cucumber.java.en.Given; +import org.junit.jupiter.api.Assertions; + +import java.time.LocalDate; + +public class ParameterTypeSteps { + + private final LocalDate expected = LocalDate.of(1907, 11, 14); + + @ParameterType("([0-9]{4})-([0-9]{2})-([0-9]{2})") + public LocalDate parameterTypeIso8601Date(String year, String month, String day) { + return LocalDate.of(Integer.parseInt(year), Integer.parseInt(month), Integer.parseInt(day)); + } + + @Given("today is {parameterTypeIso8601Date}") + public void today_is(LocalDate date) { + + Assertions.assertEquals(expected, date); + } + + @Given("tomorrow is {parameterTypeRegistryIso8601Date}") + public void tomorrow_is(LocalDate date) { + Assertions.assertEquals(expected, date); + } +} diff --git a/java8/src/test/java/cucumber/runtime/java8/test/RunCucumberTest.java b/java/src/test/java/io/cucumber/java/annotation/RunCucumberTest.java similarity index 77% rename from java8/src/test/java/cucumber/runtime/java8/test/RunCucumberTest.java rename to java/src/test/java/io/cucumber/java/annotation/RunCucumberTest.java index 2711b5a2ae..370d44f9db 100644 --- a/java8/src/test/java/cucumber/runtime/java8/test/RunCucumberTest.java +++ b/java/src/test/java/io/cucumber/java/annotation/RunCucumberTest.java @@ -1,4 +1,4 @@ -package cucumber.runtime.java8.test; +package io.cucumber.java.annotation; import io.cucumber.junit.Cucumber; import org.junit.runner.RunWith; diff --git a/java/src/test/java/cucumber/runtime/java/test/ScenarioStepDefs.java b/java/src/test/java/io/cucumber/java/annotation/ScenarioSteps.java similarity index 70% rename from java/src/test/java/cucumber/runtime/java/test/ScenarioStepDefs.java rename to java/src/test/java/io/cucumber/java/annotation/ScenarioSteps.java index b4585e4321..a3d3c3eac0 100644 --- a/java/src/test/java/cucumber/runtime/java/test/ScenarioStepDefs.java +++ b/java/src/test/java/io/cucumber/java/annotation/ScenarioSteps.java @@ -1,14 +1,14 @@ -package cucumber.runtime.java.test; +package io.cucumber.java.annotation; -import cucumber.api.Scenario; -import cucumber.api.java.Before; -import cucumber.api.java.en.Given; -import cucumber.api.java.en.Then; -import cucumber.api.java.en.When; +import io.cucumber.core.api.Scenario; +import io.cucumber.java.Before; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; import static org.junit.Assert.assertEquals; -public class ScenarioStepDefs { +public class ScenarioSteps { private String scenarioName = ""; diff --git a/java/src/test/java/io/cucumber/java/annotation/Steps.java b/java/src/test/java/io/cucumber/java/annotation/Steps.java new file mode 100644 index 0000000000..5fb8caac2f --- /dev/null +++ b/java/src/test/java/io/cucumber/java/annotation/Steps.java @@ -0,0 +1,11 @@ +package io.cucumber.java.annotation; + +import io.cucumber.java.en.Given; + +public class Steps { + + @Given("I have {int} cukes in the belly") + public void I_have_cukes_in_the_belly(int arg1) { + } + +} diff --git a/java/src/test/java/cucumber/runtime/java/test/SubstitutionStepdefs.java b/java/src/test/java/io/cucumber/java/annotation/SubstitutionSteps.java similarity index 86% rename from java/src/test/java/cucumber/runtime/java/test/SubstitutionStepdefs.java rename to java/src/test/java/io/cucumber/java/annotation/SubstitutionSteps.java index 60ff4934e5..190a983cc7 100644 --- a/java/src/test/java/cucumber/runtime/java/test/SubstitutionStepdefs.java +++ b/java/src/test/java/io/cucumber/java/annotation/SubstitutionSteps.java @@ -1,15 +1,15 @@ -package cucumber.runtime.java.test; +package io.cucumber.java.annotation; -import cucumber.api.java.en.Given; -import cucumber.api.java.en.Then; -import cucumber.api.java.en.When; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; import java.util.HashMap; import java.util.Map; import static org.junit.Assert.assertEquals; -public class SubstitutionStepdefs { +public class SubstitutionSteps { private static final Map ROLES = new HashMap() {{ put("Manager", "now able to manage your employee accounts"); put("Admin", "able to manage any user account on the system"); diff --git a/java/src/test/java/io/cucumber/java/annotation/TypeRegistryConfiguration.java b/java/src/test/java/io/cucumber/java/annotation/TypeRegistryConfiguration.java new file mode 100644 index 0000000000..8e1740902f --- /dev/null +++ b/java/src/test/java/io/cucumber/java/annotation/TypeRegistryConfiguration.java @@ -0,0 +1,27 @@ +package io.cucumber.java.annotation; + +import io.cucumber.core.api.TypeRegistry; +import io.cucumber.core.api.TypeRegistryConfigurer; +import io.cucumber.cucumberexpressions.CaptureGroupTransformer; +import io.cucumber.cucumberexpressions.ParameterType; + +import java.time.LocalDate; + +public class TypeRegistryConfiguration implements TypeRegistryConfigurer { + private final CaptureGroupTransformer localDateParameterType = + (String[] args) -> LocalDate.of( + Integer.parseInt(args[0]), + Integer.parseInt(args[1]), + Integer.parseInt(args[2]) + ); + + @Override + public void configureTypeRegistry(TypeRegistry typeRegistry) { + typeRegistry.defineParameterType(new ParameterType<>( + "parameterTypeRegistryIso8601Date", + "([0-9]{4})/([0-9]{2})/([0-9]{2})", + LocalDate.class, + localDateParameterType + )); + } +} diff --git a/java/src/test/java/cucumber/runtime/java/test/UnusedGlue.java b/java/src/test/java/io/cucumber/java/annotation/UnusedGlue.java similarity index 74% rename from java/src/test/java/cucumber/runtime/java/test/UnusedGlue.java rename to java/src/test/java/io/cucumber/java/annotation/UnusedGlue.java index 90bb7ace4b..ffa9d1f650 100644 --- a/java/src/test/java/cucumber/runtime/java/test/UnusedGlue.java +++ b/java/src/test/java/io/cucumber/java/annotation/UnusedGlue.java @@ -1,7 +1,7 @@ -package cucumber.runtime.java.test; +package io.cucumber.java.annotation; -import cucumber.api.java.Before; -import cucumber.api.java.en.Given; +import io.cucumber.java.Before; +import io.cucumber.java.en.Given; public class UnusedGlue { public UnusedGlue() { diff --git a/java/src/test/java/io/cucumber/java/defaultstransformer/DataTableSteps.java b/java/src/test/java/io/cucumber/java/defaultstransformer/DataTableSteps.java new file mode 100644 index 0000000000..11734da0b1 --- /dev/null +++ b/java/src/test/java/io/cucumber/java/defaultstransformer/DataTableSteps.java @@ -0,0 +1,117 @@ +package io.cucumber.java.defaultstransformer; + +import io.cucumber.datatable.dependency.com.fasterxml.jackson.databind.ObjectMapper; +import io.cucumber.java.DefaultDataTableCellTransformer; +import io.cucumber.java.DefaultDataTableEntryTransformer; +import io.cucumber.java.DefaultParameterTransformer; +import io.cucumber.java.Transpose; +import io.cucumber.java.en.Given; +import org.hamcrest.CoreMatchers; + +import java.lang.reflect.Type; +import java.time.LocalDate; +import java.util.Currency; +import java.util.Date; +import java.util.List; +import java.util.Objects; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.*; + +public class DataTableSteps { + + private final Author expectedAuthor = new Author("Annie M. G.", "Schmidt", "1911-03-20"); + private final ObjectMapper objectMapper = new ObjectMapper(); + + @DefaultParameterTransformer + @DefaultDataTableEntryTransformer + @DefaultDataTableCellTransformer + public Object defaultTransformer(Object fromValue, Type toValueType) { + return objectMapper.convertValue(fromValue, objectMapper.constructType(toValueType)); + } + + @Given("a list of authors in a table") + public void aListOfAuthorsInATable(List authors) { + assertTrue(authors.contains(expectedAuthor)); + } + + @Given("a single currency in a table") + public void aSingleCurrencyInATable(Currency currency) { + assertThat(currency, is(Currency.getInstance("EUR"))); + } + + @Given("a currency in a parameter {}") + public void aCurrencyInAParameter(Currency currency) { + assertThat(currency, is(Currency.getInstance("EUR"))); + } + + + public static class Author { + String firstName; + String lastName; + String birthDate; + + Author() { + } + + public Author(String firstName, String lastName, String birthDate) { + this.firstName = firstName; + this.lastName = lastName; + this.birthDate = birthDate; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getBirthDate() { + return birthDate; + } + + public void setBirthDate(String birthDate) { + this.birthDate = birthDate; + } + + @Override + public String toString() { + return "Author{" + + "firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", birthDate='" + birthDate + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Author author = (Author) o; + + if (!firstName.equals(author.firstName)) return false; + if (!lastName.equals(author.lastName)) return false; + return birthDate.equals(author.birthDate); + } + + @Override + public int hashCode() { + int result = firstName.hashCode(); + result = 31 * result + lastName.hashCode(); + result = 31 * result + birthDate.hashCode(); + return result; + } + } + +} diff --git a/java/src/test/java/io/cucumber/java/defaultstransformer/RunCucumberTest.java b/java/src/test/java/io/cucumber/java/defaultstransformer/RunCucumberTest.java new file mode 100644 index 0000000000..91c08d3924 --- /dev/null +++ b/java/src/test/java/io/cucumber/java/defaultstransformer/RunCucumberTest.java @@ -0,0 +1,8 @@ +package io.cucumber.java.defaultstransformer; + +import io.cucumber.junit.Cucumber; +import org.junit.runner.RunWith; + +@RunWith(Cucumber.class) +public class RunCucumberTest { +} diff --git a/java/src/test/java/io/cucumber/java/incorrectlysubclassedsteps/SubclassesSteps.java b/java/src/test/java/io/cucumber/java/incorrectlysubclassedsteps/SubclassesSteps.java new file mode 100644 index 0000000000..b13228ab28 --- /dev/null +++ b/java/src/test/java/io/cucumber/java/incorrectlysubclassedsteps/SubclassesSteps.java @@ -0,0 +1,6 @@ +package io.cucumber.java.incorrectlysubclassedsteps; + +import io.cucumber.java.steps.Steps; + +public class SubclassesSteps extends Steps { +} diff --git a/java/src/test/java/io/cucumber/java/repeatable/Steps.java b/java/src/test/java/io/cucumber/java/repeatable/Steps.java new file mode 100644 index 0000000000..7fa5f3081a --- /dev/null +++ b/java/src/test/java/io/cucumber/java/repeatable/Steps.java @@ -0,0 +1,12 @@ +package io.cucumber.java.repeatable; + +import io.cucumber.java.en.Given; + +public class Steps { + + @Given("test") + @Given("test again") + public void test() { + + } +} diff --git a/java/src/test/java/io/cucumber/java/steps/Steps.java b/java/src/test/java/io/cucumber/java/steps/Steps.java new file mode 100644 index 0000000000..109bdc4c89 --- /dev/null +++ b/java/src/test/java/io/cucumber/java/steps/Steps.java @@ -0,0 +1,11 @@ +package io.cucumber.java.steps; + +import io.cucumber.java.en.Given; + +public class Steps { + + @Given("test") + public void test() { + + } +} diff --git a/java/src/test/resources/cucumber/runtime/java/test/french-iso-8859-1-cukes.feature b/java/src/test/resources/cucumber/runtime/java/test/french-iso-8859-1-cukes.feature deleted file mode 100644 index 91254aead4..0000000000 --- a/java/src/test/resources/cucumber/runtime/java/test/french-iso-8859-1-cukes.feature +++ /dev/null @@ -1,5 +0,0 @@ -# language: fr -# encoding: ISO-8859-1 -Fonctionnalité: Cukes - Scénario: in the belly - Étant donné I have 5 cukes in the belly diff --git a/java/src/test/resources/cucumber/runtime/java/test/table_conversion.feature b/java/src/test/resources/cucumber/runtime/java/test/table_conversion.feature deleted file mode 100644 index 7a4881ae4b..0000000000 --- a/java/src/test/resources/cucumber/runtime/java/test/table_conversion.feature +++ /dev/null @@ -1,7 +0,0 @@ -Feature: Table Conversion - - Scenario: use a table - Given this data table: - | first | last | - | Aslak | Hellesøy | - | Donald | Duck | diff --git a/java/src/test/resources/cucumber/runtime/java/test/cukes.feature b/java/src/test/resources/io/cucumber/java/annotation/cukes.feature similarity index 100% rename from java/src/test/resources/cucumber/runtime/java/test/cukes.feature rename to java/src/test/resources/io/cucumber/java/annotation/cukes.feature diff --git a/java/src/test/resources/cucumber/runtime/java/test/authors.feature b/java/src/test/resources/io/cucumber/java/annotation/data-table.feature similarity index 63% rename from java/src/test/resources/cucumber/runtime/java/test/authors.feature rename to java/src/test/resources/io/cucumber/java/annotation/data-table.feature index 7b2c57d8d0..bcf1b280a6 100644 --- a/java/src/test/resources/cucumber/runtime/java/test/authors.feature +++ b/java/src/test/resources/io/cucumber/java/annotation/data-table.feature @@ -1,6 +1,6 @@ -Feature: Authors and tables +Feature: Datatable - Scenario: Some authors and tables + Scenario: Convert a table to a generic list via the ParameterTypeRegistry Given a list of authors in a table | firstName | lastName | birthDate | | Annie M. G. | Schmidt | 1911-03-20 | @@ -11,6 +11,8 @@ Feature: Authors and tables | lastName | Schmidt | Dahl | | birthDate | 1911-03-20 | 1916-09-13 | + Scenario: Convert a table to a single object via the ParameterTypeRegistry + Given a single author in a table | firstName | lastName | birthDate | | Annie M. G. | Schmidt | 1911-03-20 | @@ -20,3 +22,10 @@ Feature: Authors and tables | lastName | Schmidt | | birthDate | 1911-03-20 | + + Scenario: Convert a table to a generic list via the @DataTableType Annotation + + Given a list of people in a table + | first | last | + | Astrid | Lindgren | + | Roald | Dahl | \ No newline at end of file diff --git a/java/src/test/resources/io/cucumber/java/annotation/french-iso-8859-1-cukes.feature b/java/src/test/resources/io/cucumber/java/annotation/french-iso-8859-1-cukes.feature new file mode 100644 index 0000000000..8a270c523e --- /dev/null +++ b/java/src/test/resources/io/cucumber/java/annotation/french-iso-8859-1-cukes.feature @@ -0,0 +1,6 @@ +# language: fr +# encoding: ISO-8859-1 +Fonctionnalité: Concombres dans ISO-8859-1 + + Scénario: dans la ventre + Étant donné j'ai 5 concombres diff --git a/java/src/test/resources/io/cucumber/java/annotation/french-numbers.feature b/java/src/test/resources/io/cucumber/java/annotation/french-numbers.feature new file mode 100644 index 0000000000..a85d3174bb --- /dev/null +++ b/java/src/test/resources/io/cucumber/java/annotation/french-numbers.feature @@ -0,0 +1,5 @@ +# language: fr +Fonctionnalité: Concombres fractionnaires + + Scénario: dans la ventre + Étant donné j'ai 5,5 concombres fractionnaires diff --git a/java/src/test/resources/io/cucumber/java/annotation/parameter-types.feature b/java/src/test/resources/io/cucumber/java/annotation/parameter-types.feature new file mode 100644 index 0000000000..84894790fd --- /dev/null +++ b/java/src/test/resources/io/cucumber/java/annotation/parameter-types.feature @@ -0,0 +1,7 @@ +Feature: ParameterTypes + + Scenario: Convert a parameter to date via the ParameterTypeRegistry + Given tomorrow is 1907/11/14 + + Scenario: Convert a parameter to date via the @ParameterType Annotation + Given today is 1907-11-14 diff --git a/java/src/test/resources/cucumber/runtime/java/test/scenario.feature b/java/src/test/resources/io/cucumber/java/annotation/scenario.feature similarity index 100% rename from java/src/test/resources/cucumber/runtime/java/test/scenario.feature rename to java/src/test/resources/io/cucumber/java/annotation/scenario.feature diff --git a/java/src/test/resources/cucumber/runtime/java/test/scenario_outline_substitution.feature b/java/src/test/resources/io/cucumber/java/annotation/scenario_outline_substitution.feature similarity index 100% rename from java/src/test/resources/cucumber/runtime/java/test/scenario_outline_substitution.feature rename to java/src/test/resources/io/cucumber/java/annotation/scenario_outline_substitution.feature diff --git a/java/src/test/resources/io/cucumber/java/defaultstransformer/default-transformer.feature b/java/src/test/resources/io/cucumber/java/defaultstransformer/default-transformer.feature new file mode 100644 index 0000000000..003d72051f --- /dev/null +++ b/java/src/test/resources/io/cucumber/java/defaultstransformer/default-transformer.feature @@ -0,0 +1,17 @@ +Feature: Datatable + + Scenario: Convert a table to a generic list via default transformer + Given a list of authors in a table + | firstName | lastName | birthDate | + | Annie M. G. | Schmidt | 1911-03-20 | + | Roald | Dahl | 1916-09-13 | + + + Scenario: Convert a table to a single object via the default transformer + + Given a single currency in a table + | EUR | + + Scenario: Convert an anonymous parameter to a single object via default transformer + + Given a currency in a parameter EUR diff --git a/java8/pom.xml b/java8/pom.xml index 3c3d775fc0..d3f991d9e8 100644 --- a/java8/pom.xml +++ b/java8/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 4.7.2-SNAPSHOT + 5.0.0-SNAPSHOT cucumber-java8 @@ -19,7 +19,7 @@ io.cucumber - cucumber-java + cucumber-core org.apiguardian @@ -36,9 +36,28 @@ cucumber-junit test + + + org.hamcrest + hamcrest-library + test + + + + org.junit.jupiter + junit-jupiter-api + test + + + + org.junit.vintage + junit-vintage-engine + test + + - junit - junit + org.mockito + mockito-core test @@ -82,17 +101,6 @@ - - org.apache.maven.plugins - maven-compiler-plugin - - true - UTF-8 - 1.8 - 1.8 - -XDignore.symbol.file=true - - diff --git a/java8/src/main/groovy/deprecated-lambda.java.gsp b/java8/src/main/groovy/deprecated-lambda.java.gsp deleted file mode 100644 index 03d9462475..0000000000 --- a/java8/src/main/groovy/deprecated-lambda.java.gsp +++ /dev/null @@ -1,113 +0,0 @@ -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; - -/** - * ${locale.getDisplayLanguage()} - *

- * 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. - * - * @deprecated use {@link io.cucumber.java8.${className}} instead. - */ -@Deprecated -public interface ${className} extends LambdaGlueBase { -<% i18n.stepKeywords.findAll { !it.contains('*') && !it.matches("^\\d.*") }.sort().unique().each { kw -> %> - /** - * 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) - ); - } - - /** - * Creates a new step definition. - * - * The timeout controls how long step is allowed to run. Cucumber - * will mark the step as failed when exceeded. When the maximum - * duration is exceeded the thread will receive an in interrupt. - * Note: if the interrupt is ignored cucumber will wait for the this - * step to finish. - * - * @param expression the cucumber expression - * @param timeoutMillis timeout in milliseconds. 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(",") %> - /** - * Creates a new step definition. - * - * @param expression the cucumber expression - * @param body a lambda expression with ${arity} parameters - * <% (1..arity).each { i -> %> - * @param type of argument ${i} <% } %> - */ - default <${genericSignature}> void ${java.text.Normalizer.normalize(kw.replaceAll("[\\s',!]", ""), java.text.Normalizer.Form.NFC)}(String expression, A${arity}<${genericSignature}> body) { - LambdaGlueRegistry.INSTANCE.get().addStepDefinition((typeRegistry) -> - Java8StepDefinition.create(expression, A${arity}.class, body, typeRegistry) - ); - } - - /** - * Creates a new step definition. - * - * The timeout controls how long step is allowed to run. Cucumber - * will mark the step as failed when exceeded. When the maximum - * duration is exceeded the thread will receive an in interrupt. - * Note: if the interrupt is ignored cucumber will wait for the this - * step to finish. - * - * @param expression the cucumber expression - * @param timeoutMillis timeout in milliseconds. 0 (default) means no restriction. - * @param body a lambda expression with ${arity} parameters - * <% (1..arity).each { i -> %> - * @param type of argument ${i} <% } %> - */ - 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/groovy/generate-interfaces.groovy b/java8/src/main/groovy/generate-interfaces.groovy index 51329c16ca..8166a716e7 100644 --- a/java8/src/main/groovy/generate-interfaces.groovy +++ b/java8/src/main/groovy/generate-interfaces.groovy @@ -19,20 +19,13 @@ GherkinDialectProvider.DIALECTS.keySet().each { language -> def dialect = dialectProvider.getDialect(language, null) def normalized_language = dialect.language.replaceAll("[\\s-]", "_").toLowerCase() if (!unsupported.contains(normalized_language)) { + def templateSource = new File(project.baseDir, "src/main/groovy/lambda.java.gsp").getText() def className = "${normalized_language}".capitalize() def locale = localeFor(dialect.language) def binding = [ "i18n":dialect, "className":className, "locale": locale ] - - def templateSource = new File(project.baseDir, "src/main/groovy/lambda.java.gsp").getText() def template = engine.createTemplate(templateSource).make(binding) def file = new File(project.baseDir, "target/generated-sources/i18n/java/io/cucumber/java8/${className}.java") file.parentFile.mkdirs() file.write(template.toString(), "UTF-8") - - def deprecatedTemplateSource = new File(project.baseDir, "src/main/groovy/deprecated-lambda.java.gsp").getText() - def deprecatedTemplate = engine.createTemplate(deprecatedTemplateSource).make(binding) - def deprecatedFile = new File(project.baseDir, "target/generated-sources/i18n/java/cucumber/api/java8/${className}.java") - deprecatedFile.parentFile.mkdirs() - deprecatedFile.write(deprecatedTemplate.toString(), "UTF-8") } } diff --git a/java8/src/main/groovy/lambda.java.gsp b/java8/src/main/groovy/lambda.java.gsp index 17130c82c4..f0e5313b90 100644 --- a/java8/src/main/groovy/lambda.java.gsp +++ b/java8/src/main/groovy/lambda.java.gsp @@ -11,8 +11,8 @@ import io.cucumber.java8.StepdefBody.A7; import io.cucumber.java8.StepdefBody.A8; import io.cucumber.java8.StepdefBody.A9; -import cucumber.runtime.java.LambdaGlueRegistry; -import cucumber.runtime.java8.Java8StepDefinition; +import io.cucumber.java8.LambdaGlueRegistry; +import io.cucumber.java8.Java8StepDefinition; import io.cucumber.java8.LambdaGlue; import org.apiguardian.api.API; @@ -46,9 +46,7 @@ public interface ${className} extends LambdaGlue { * @param body a lambda expression with no parameters */ default void ${java.text.Normalizer.normalize(kw.replaceAll("[\\s',!]", ""), java.text.Normalizer.Form.NFC)}(String expression, A0 body) { - LambdaGlueRegistry.INSTANCE.get().addStepDefinition((typeRegistry) -> - Java8StepDefinition.create(expression, A0.class, body, typeRegistry) - ); + LambdaGlueRegistry.INSTANCE.get().addStepDefinition(Java8StepDefinition.create(expression, A0.class, body)); } /** @@ -63,11 +61,12 @@ public interface ${className} extends LambdaGlue { * @param expression the cucumber expression * @param timeoutMillis timeout in milliseconds. 0 (default) means no restriction. * @param body a lambda expression with no parameters + * @deprecated use a library based solution instead. E.g. Awaitility + * or JUnit 5s Assertions.assertTimeout. */ + @Deprecated default void ${java.text.Normalizer.normalize(kw.replaceAll("[\\s',!]", ""), java.text.Normalizer.Form.NFC)}(String expression, long timeoutMillis, A0 body) { - LambdaGlueRegistry.INSTANCE.get().addStepDefinition((typeRegistry) -> - Java8StepDefinition.create(expression, timeoutMillis, A0.class, body, typeRegistry) - ); + LambdaGlueRegistry.INSTANCE.get().addStepDefinition(Java8StepDefinition.create(expression, timeoutMillis, A0.class, body)); } <% (1..9).each { arity -> @@ -82,9 +81,7 @@ public interface ${className} extends LambdaGlue { * @param type of argument ${i} <% } %> */ default <${genericSignature}> void ${java.text.Normalizer.normalize(kw.replaceAll("[\\s',!]", ""), java.text.Normalizer.Form.NFC)}(String expression, A${arity}<${genericSignature}> body) { - LambdaGlueRegistry.INSTANCE.get().addStepDefinition((typeRegistry) -> - Java8StepDefinition.create(expression, A${arity}.class, body, typeRegistry) - ); + LambdaGlueRegistry.INSTANCE.get().addStepDefinition(Java8StepDefinition.create(expression, A${arity}.class, body)); } /** @@ -101,11 +98,12 @@ public interface ${className} extends LambdaGlue { * @param body a lambda expression with ${arity} parameters * <% (1..arity).each { i -> %> * @param type of argument ${i} <% } %> + * @deprecated use a library based solution instead. E.g. Awaitility + * or JUnit 5s Assertions.assertTimeout. */ + @Deprecated default <${genericSignature}> void ${java.text.Normalizer.normalize(kw.replaceAll("[\\s',!]", ""), java.text.Normalizer.Form.NFC)}(String expression, long timeoutMillis, A${arity}<${genericSignature}> body) { - LambdaGlueRegistry.INSTANCE.get().addStepDefinition((typeRegistry) -> - Java8StepDefinition.create(expression, timeoutMillis, A${arity}.class, body, typeRegistry) - ); + LambdaGlueRegistry.INSTANCE.get().addStepDefinition(Java8StepDefinition.create(expression, timeoutMillis, A${arity}.class, body)); } <% } %> diff --git a/java8/src/main/java/cucumber/api/java8/HookBody.java b/java8/src/main/java/cucumber/api/java8/HookBody.java deleted file mode 100644 index 2f0c6895d4..0000000000 --- a/java8/src/main/java/cucumber/api/java8/HookBody.java +++ /dev/null @@ -1,8 +0,0 @@ -package cucumber.api.java8; - -import cucumber.api.Scenario; - -@Deprecated -public interface HookBody { - void accept(Scenario scenario) throws Throwable; -} diff --git a/java8/src/main/java/cucumber/api/java8/HookNoArgsBody.java b/java8/src/main/java/cucumber/api/java8/HookNoArgsBody.java deleted file mode 100644 index 0ede4b5710..0000000000 --- a/java8/src/main/java/cucumber/api/java8/HookNoArgsBody.java +++ /dev/null @@ -1,6 +0,0 @@ -package cucumber.api.java8; - -@Deprecated -public interface HookNoArgsBody { - void accept() throws Throwable; -} diff --git a/java8/src/main/java/cucumber/api/java8/StepdefBody.java b/java8/src/main/java/cucumber/api/java8/StepdefBody.java deleted file mode 100644 index 61a9362786..0000000000 --- a/java8/src/main/java/cucumber/api/java8/StepdefBody.java +++ /dev/null @@ -1,44 +0,0 @@ -package cucumber.api.java8; - -@Deprecated -public interface StepdefBody { - public static interface A0 extends StepdefBody { - void accept() throws Throwable; - } - - public static interface A1 extends StepdefBody { - void accept(T1 p1) throws Throwable; - } - - public static interface A2 extends StepdefBody { - void accept(T1 p1, T2 p2) throws Throwable; - } - - public static interface A3 extends StepdefBody { - void accept(T1 p1, T2 p2, T3 p3) throws Throwable; - } - - public static interface A4 extends StepdefBody { - void accept(T1 p1, T2 p2, T3 p3, T4 p4) throws Throwable; - } - - public static interface A5 extends StepdefBody { - void accept(T1 p1, T2 p2, T3 p3, T4 p4, T5 p5) throws Throwable; - } - - public static interface A6 extends StepdefBody { - void accept(T1 p1, T2 p2, T3 p3, T4 p4, T5 p5, T6 p6) throws Throwable; - } - - public static interface A7 extends StepdefBody { - void accept(T1 p1, T2 p2, T3 p3, T4 p4, T5 p5, T6 p6, T7 p7) throws Throwable; - } - - public static interface A8 extends StepdefBody { - void accept(T1 p1, T2 p2, T3 p3, T4 p4, T5 p5, T6 p6, T7 p7, T8 p8) throws Throwable; - } - - public static interface A9 extends StepdefBody { - void accept(T1 p1, T2 p2, T3 p3, T4 p4, T5 p5, T6 p6, T7 p7, T8 p8, T9 p9) throws Throwable; - } -} diff --git a/java8/src/main/java/cucumber/runtime/java8/Java8HookDefinition.java b/java8/src/main/java/cucumber/runtime/java8/Java8HookDefinition.java deleted file mode 100644 index 0b77fc99da..0000000000 --- a/java8/src/main/java/cucumber/runtime/java8/Java8HookDefinition.java +++ /dev/null @@ -1,145 +0,0 @@ -package cucumber.runtime.java8; - -import static java.util.Arrays.asList; - -import cucumber.api.Scenario; -import cucumber.api.java8.HookBody; -import cucumber.api.java8.HookNoArgsBody; -import cucumber.runtime.HookDefinition; -import cucumber.runtime.ScenarioScoped; -import cucumber.runtime.filter.TagPredicate; -import cucumber.runtime.Timeout; -import gherkin.pickles.PickleTag; -import io.cucumber.core.event.Status; - -import java.util.Collection; - -public class Java8HookDefinition implements HookDefinition, ScenarioScoped { - private final TagPredicate tagPredicate; - private final int order; - private final long timeoutMillis; - private final HookNoArgsBody hookNoArgsBody; - private HookBody hookBody; - private final StackTraceElement location; - - private Java8HookDefinition(String[] tagExpressions, int order, long timeoutMillis, HookBody hookBody, HookNoArgsBody hookNoArgsBody) { - this.order = order; - this.timeoutMillis = timeoutMillis; - this.tagPredicate = new TagPredicate(asList(tagExpressions)); - this.hookBody = hookBody; - this.hookNoArgsBody = hookNoArgsBody; - this.location = new Exception().getStackTrace()[3]; - } - - public Java8HookDefinition(String[] tagExpressions, int order, long timeoutMillis, HookBody hookBody) { - this(tagExpressions, order, timeoutMillis, hookBody, null); - } - - public Java8HookDefinition(String[] tagExpressions, int order, long timeoutMillis, HookNoArgsBody hookNoArgsBody) { - this(tagExpressions, order, timeoutMillis, null, hookNoArgsBody); - } - - public Java8HookDefinition(String tagExpression, int order, long timeoutMillis, io.cucumber.java8.HookBody hookBody) { - this(new String[]{tagExpression}, order, timeoutMillis, scenario -> hookBody.accept(new ScenarioAdaptor(scenario))); - } - - public Java8HookDefinition(String tagExpression, int order, long timeoutMillis, io.cucumber.java8.HookNoArgsBody hookNoArgsBody) { - this(new String[]{tagExpression}, order, timeoutMillis, hookNoArgsBody::accept); - } - - @Override - public String getLocation(boolean detail) { - return location.getFileName() + ":" + location.getLineNumber(); - } - - @Override - public void execute(final Scenario scenario) throws Throwable { - Timeout.timeout(() -> { - if (hookBody != null) { - hookBody.accept(scenario); - } else { - hookNoArgsBody.accept(); - } - return null; - - }, timeoutMillis); - } - - @Override - public boolean matches(Collection tags) { - return tagPredicate.apply(tags); - } - - @Override - public int getOrder() { - return order; - } - - @Override - public boolean isScenarioScoped() { - return true; - } - - @Override - public void disposeScenarioScope() { - this.hookBody = null; - } - - private static class ScenarioAdaptor implements io.cucumber.core.api.Scenario { - private final Scenario scenario; - - ScenarioAdaptor(Scenario scenario) { - this.scenario = scenario; - } - - @Override - public Collection getSourceTagNames() { - return scenario.getSourceTagNames(); - } - - @Override - public Status getStatus() { - return Status.valueOf(scenario.getStatus().name()); - } - - @Override - public boolean isFailed() { - return scenario.isFailed(); - } - - @Override - public void embed(byte[] data, String mimeType) { - scenario.embed(data, mimeType); - } - - @Override - public void embed(byte[] data, String mimeType, String name) { - scenario.embed(data, mimeType, name); - } - - @Override - public void write(String text) { - scenario.write(text); - } - - @Override - public String getName() { - return scenario.getName(); - } - - @Override - public String getId() { - return scenario.getId(); - } - - @Override - public String getUri() { - return scenario.getUri(); - } - - @Override - public Integer getLine() { - return scenario.getLines().get(0); - } - } -} diff --git a/java8/src/main/java/cucumber/runtime/java8/Java8StepDefinition.java b/java8/src/main/java/cucumber/runtime/java8/Java8StepDefinition.java deleted file mode 100644 index 8ae81887fc..0000000000 --- a/java8/src/main/java/cucumber/runtime/java8/Java8StepDefinition.java +++ /dev/null @@ -1,170 +0,0 @@ -package cucumber.runtime.java8; - -import static cucumber.runtime.java8.ParameterInfo.fromTypes; -import static java.lang.String.format; -import static net.jodah.typetools.TypeResolver.resolveRawArguments; - -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.ScenarioScoped; -import io.cucumber.stepexpression.ExpressionArgumentMatcher; -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 net.jodah.typetools.TypeResolver.Unknown; - -import java.lang.reflect.Method; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -public class Java8StepDefinition implements StepDefinition, ScenarioScoped { - - public static Java8StepDefinition create( - String expression, Class bodyClass, T body, TypeRegistry typeRegistry) { - return new Java8StepDefinition(expression, 0, bodyClass, body, typeRegistry); - } - - 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 StepdefBody body; - - private final StepExpression expression; - private final StackTraceElement location; - - private final List parameterInfos; - private final Method method; - - private Java8StepDefinition(String expression, - long timeoutMillis, - Class bodyClass, - T body, - TypeRegistry typeRegistry) { - this.timeoutMillis = timeoutMillis; - this.body = body; - - this.location = new Exception().getStackTrace()[5]; - this.method = getAcceptMethod(body.getClass()); - 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<>(); - for (Method method : bodyClass.getDeclaredMethods()) { - if (!method.isBridge() && !method.isSynthetic() && "accept".equals(method.getName())) { - acceptMethods.add(method); - } - } - if (acceptMethods.size() != 1) { - throw new IllegalStateException(format( - "Expected single 'accept' method on body class, found '%s'", acceptMethods)); - } - return acceptMethods.get(0); - } - - private CucumberException withLocation(CucumberException exception) { - exception.setStackTrace(new StackTraceElement[]{this.location}); - return exception; - } - - @Override - public List matchedArguments(PickleStep step) { - ArgumentMatcher argumentMatcher = new ExpressionArgumentMatcher(expression); - Type[] types = new Type[parameterInfos.size()]; - for (int i = 0; i < types.length; i++) { - types[i] = parameterInfos.get(i).getType(); - } - return argumentMatcher.argumentsFrom(step, types); - } - - @Override - public String getLocation(boolean detail) { - return location.getFileName() + ":" + location.getLineNumber(); - } - - @Override - public Integer getParameterCount() { - return parameterInfos.size(); - } - - @Override - public void execute(final Object[] args) throws Throwable { - Utils.invoke(body, method, timeoutMillis, args); - } - - @Override - public boolean isDefinedAt(StackTraceElement stackTraceElement) { - return location.getFileName() != null && location.getFileName().equals(stackTraceElement.getFileName()); - } - - @Override - public String getPattern() { - 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() { - Type type = parameterInfo.getType(); - if (Unknown.class.equals(type)) { - return Object.class; - } - - return requireNonMapOrListType(type); - } - - private Type requireNonMapOrListType(Type argumentType) { - if (argumentType instanceof Class) { - Class argumentClass = (Class) argumentType; - if (List.class.isAssignableFrom(argumentClass) || Map.class.isAssignableFrom(argumentClass)) { - throw withLocation( - new CucumberException( - format("Can't use %s in lambda step definition \"%s\". " + - "Declare a DataTable argument instead and convert " + - "manually with asList/asLists/asMap/asMaps", - argumentClass.getName(), expression.getSource()))); - } - } - return argumentType; - } - } - - @Override - public void disposeScenarioScope() { - this.body = null; - } -} diff --git a/java8/src/main/java/cucumber/runtime/java8/LambdaGlueBase.java b/java8/src/main/java/cucumber/runtime/java8/LambdaGlueBase.java deleted file mode 100644 index 6eb6215a9c..0000000000 --- a/java8/src/main/java/cucumber/runtime/java8/LambdaGlueBase.java +++ /dev/null @@ -1,176 +0,0 @@ -package cucumber.runtime.java8; - -import cucumber.api.java8.GlueBase; -import cucumber.api.java8.HookBody; -import cucumber.api.java8.HookNoArgsBody; -import cucumber.runtime.java.JavaBackend; - -@Deprecated -public interface LambdaGlueBase extends GlueBase { - - String[] EMPTY_TAG_EXPRESSIONS = new String[0]; - long NO_TIMEOUT = 0; - int DEFAULT_BEFORE_ORDER = 0; - int DEFAULT_AFTER_ORDER = 1000; - - default void Before(final HookBody body) { - JavaBackend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); - } - - default void Before(String[] tagExpressions, final HookBody body) { - JavaBackend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(tagExpressions, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); - } - - default void Before(long timeoutMillis, final HookBody body) { - JavaBackend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, timeoutMillis, body)); - } - - default void Before(int order, final HookBody body) { - JavaBackend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); - } - - default void Before(String[] tagExpressions, long timeoutMillis, int order, final HookBody body) { - JavaBackend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(tagExpressions, order, timeoutMillis, body)); - } - - default void Before(final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); - } - - default void Before(String[] tagExpressions, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(tagExpressions, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); - } - - default void Before(long timeoutMillis, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, timeoutMillis, body)); - } - - default void Before(int order, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); - } - - default void Before(String[] tagExpressions, long timeoutMillis, int order, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(tagExpressions, order, timeoutMillis, body)); - } - - default void BeforeStep(final HookBody body) { - JavaBackend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); - } - - default void BeforeStep(String[] tagExpressions, final HookBody body) { - JavaBackend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(tagExpressions, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); - } - - default void BeforeStep(long timeoutMillis, final HookBody body) { - JavaBackend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, timeoutMillis, body)); - } - - default void BeforeStep(int order, final HookBody body) { - JavaBackend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); - } - - default void BeforeStep(String[] tagExpressions, long timeoutMillis, int order, final HookBody body) { - JavaBackend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(tagExpressions, order, timeoutMillis, body)); - } - - default void BeforeStep(final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); - } - - default void BeforeStep(String[] tagExpressions, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(tagExpressions, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); - } - - default void BeforeStep(long timeoutMillis, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, timeoutMillis, body)); - } - - default void BeforeStep(int order, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); - } - - default void BeforeStep(String[] tagExpressions, long timeoutMillis, int order, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(tagExpressions, order, timeoutMillis, body)); - } - - default void After(final HookBody body) { - JavaBackend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); - } - - default void After(String[] tagExpressions, final HookBody body) { - JavaBackend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(tagExpressions, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); - } - - default void After(long timeoutMillis, final HookBody body) { - JavaBackend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, timeoutMillis, body)); - } - - default void After(int order, final HookBody body) { - JavaBackend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); - } - - default void After(String[] tagExpressions, long timeoutMillis, int order, final HookBody body) { - JavaBackend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(tagExpressions, order, timeoutMillis, body)); - } - - default void After(final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); - } - - default void After(String[] tagExpressions, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(tagExpressions, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); - } - - default void After(long timeoutMillis, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, timeoutMillis, body)); - } - - default void After(int order, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); - } - - default void After(String[] tagExpressions, long timeoutMillis, int order, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(tagExpressions, order, timeoutMillis, body)); - } - - default void AfterStep(final HookBody body) { - JavaBackend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); - } - - default void AfterStep(String[] tagExpressions, final HookBody body) { - JavaBackend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(tagExpressions, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); - } - - default void AfterStep(long timeoutMillis, final HookBody body) { - JavaBackend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, timeoutMillis, body)); - } - - default void AfterStep(int order, final HookBody body) { - JavaBackend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); - } - - default void AfterStep(String[] tagExpressions, long timeoutMillis, int order, final HookBody body) { - JavaBackend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(tagExpressions, order, timeoutMillis, body)); - } - - default void AfterStep(final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); - } - - default void AfterStep(String[] tagExpressions, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(tagExpressions, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); - } - - default void AfterStep(long timeoutMillis, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, timeoutMillis, body)); - } - - default void AfterStep(int order, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); - } - - default void AfterStep(String[] tagExpressions, long timeoutMillis, int order, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(tagExpressions, order, timeoutMillis, body)); - } - -} diff --git a/java8/src/main/java/cucumber/runtime/java8/ParameterInfo.java b/java8/src/main/java/cucumber/runtime/java8/ParameterInfo.java deleted file mode 100644 index 17cbb5a305..0000000000 --- a/java8/src/main/java/cucumber/runtime/java8/ParameterInfo.java +++ /dev/null @@ -1,31 +0,0 @@ -package cucumber.runtime.java8; - -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; - -class ParameterInfo { - private final Type type; - - static List fromTypes(Type[] genericParameterTypes) { - List result = new ArrayList<>(); - for (Type genericParameterType : genericParameterTypes) { - result.add(new ParameterInfo(genericParameterType)); - } - return result; - } - - private ParameterInfo(Type type) { - this.type = type; - } - - Type getType() { - return type; - } - - @Override - public String toString() { - return type.toString(); - } - -} diff --git a/java8/src/main/java/io/cucumber/java8/AbstractGlueDefinition.java b/java8/src/main/java/io/cucumber/java8/AbstractGlueDefinition.java new file mode 100644 index 0000000000..1df29ab2c3 --- /dev/null +++ b/java8/src/main/java/io/cucumber/java8/AbstractGlueDefinition.java @@ -0,0 +1,49 @@ +package io.cucumber.java8; + +import io.cucumber.core.runner.ScenarioScoped; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import static java.lang.String.format; + +abstract class AbstractGlueDefinition implements ScenarioScoped { + + Object body; + final Method method; + final StackTraceElement location; + + AbstractGlueDefinition(Object body, StackTraceElement location) { + this.body = body; + this.method = getAcceptMethod(body.getClass()); + this.location = location; + } + + public final String getLocation(boolean detail) { + return location.getFileName() + ":" + location.getLineNumber(); + } + + public final boolean isDefinedAt(StackTraceElement stackTraceElement) { + return location.getFileName() != null && location.getFileName().equals(stackTraceElement.getFileName()); + } + + private Method getAcceptMethod(Class bodyClass) { + 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(format( + "Expected single 'accept' method on body class, found '%s'", acceptMethods)); + } + return acceptMethods.get(0); + } + + @Override + public final void disposeScenarioScope() { + this.body = null; + } +} diff --git a/java8/src/main/java/io/cucumber/java8/AbstractJavaSnippet.java b/java8/src/main/java/io/cucumber/java8/AbstractJavaSnippet.java new file mode 100644 index 0000000000..f80a6d9696 --- /dev/null +++ b/java8/src/main/java/io/cucumber/java8/AbstractJavaSnippet.java @@ -0,0 +1,53 @@ +package io.cucumber.java8; + +import io.cucumber.core.snippets.Snippet; +import io.cucumber.datatable.DataTable; + +import java.lang.reflect.Type; +import java.util.Map; + +abstract class AbstractJavaSnippet implements Snippet { + @Override + public final String arguments(Map arguments) { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (Map.Entry argType : arguments.entrySet()) { + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append(getArgType(argType.getValue())).append(" ").append(argType.getKey()); + } + return sb.toString(); + } + + private String getArgType(Type argType) { + if (argType instanceof Class) { + Class cType = (Class) argType; + if (cType.equals(DataTable.class)) { + return cType.getName(); + } + return cType.getSimpleName(); + } + + // Got a better idea? Send a PR. + return argType.toString(); + } + + @Override + public final String tableHint() { + return "" + + " // For automatic transformation, change DataTable to one of\n" + + " // E, 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 final String escapePattern(String pattern) { + return pattern.replace("\\", "\\\\").replace("\"", "\\\""); + } +} diff --git a/java8/src/main/java/io/cucumber/java8/Java8Backend.java b/java8/src/main/java/io/cucumber/java8/Java8Backend.java new file mode 100644 index 0000000000..45ebf28b4f --- /dev/null +++ b/java8/src/main/java/io/cucumber/java8/Java8Backend.java @@ -0,0 +1,110 @@ +package io.cucumber.java8; + +import io.cucumber.core.backend.Backend; +import io.cucumber.core.backend.Container; +import io.cucumber.core.backend.Glue; +import io.cucumber.core.backend.HookDefinition; +import io.cucumber.core.backend.Lookup; +import io.cucumber.core.backend.StepDefinition; +import io.cucumber.core.io.ClassFinder; +import io.cucumber.core.io.ResourceLoader; +import io.cucumber.core.io.ResourceLoaderClassFinder; +import io.cucumber.core.snippets.Snippet; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import static java.lang.Thread.currentThread; + +final class Java8Backend implements Backend { + + private final Lookup lookup; + private final Container container; + private final ClassFinder classFinder; + + private List> lambdaGlueClasses = new ArrayList<>(); + private Glue glue; + + Java8Backend(Lookup lookup, Container container, ResourceLoader resourceLoader) { + this.classFinder = new ResourceLoaderClassFinder(resourceLoader, currentThread().getContextClassLoader()); + this.container = container; + this.lookup = lookup; + } + + @Override + public void loadGlue(Glue glue, List gluePaths) { + this.glue = glue; + // Scan for Java8 style glue (lambdas) + gluePaths.stream() + .map(packageName -> classFinder.getDescendants(LambdaGlue.class, packageName)) + .flatMap(Collection::stream) + .filter(glueClass -> !glueClass.isInterface()) + .filter(glueClass -> glueClass.getConstructors().length > 0) + .forEach(glueClass -> { + if (container.addClass(glueClass)) { + lambdaGlueClasses.add(glueClass); + } + }); + } + + @Override + public void buildWorld() { + // Instantiate all the stepdef classes for java8 - the stepdef will be initialised + // in the constructor. + LambdaGlueRegistry.INSTANCE.set(new GlueAdaptor(glue)); + for (Class lambdaGlueClass : lambdaGlueClasses) { + lookup.getInstance(lambdaGlueClass); + } + } + + @Override + public void disposeWorld() { + LambdaGlueRegistry.INSTANCE.remove(); + } + + @Override + public Snippet getSnippet() { + return new Java8Snippet(); + } + + + private static final class GlueAdaptor implements LambdaGlueRegistry { + + private final Glue glue; + + private GlueAdaptor(Glue glue) { + this.glue = glue; + } + + @Override + public void addStepDefinition(StepDefinition stepDefinition) { + glue.addStepDefinition(stepDefinition); + } + + @Override + public void addBeforeStepHookDefinition(HookDefinition beforeStepHook) { + glue.addBeforeStepHook(beforeStepHook); + + } + + @Override + public void addAfterStepHookDefinition(HookDefinition afterStepHook) { + glue.addAfterStepHook(afterStepHook); + + } + + @Override + public void addBeforeHookDefinition(HookDefinition beforeHook) { + glue.addBeforeHook(beforeHook); + + } + + @Override + public void addAfterHookDefinition(HookDefinition afterHook) { + glue.addAfterHook(afterHook); + + } + } +} diff --git a/java8/src/main/java/io/cucumber/java8/Java8BackendProviderService.java b/java8/src/main/java/io/cucumber/java8/Java8BackendProviderService.java new file mode 100644 index 0000000000..c4c4175fce --- /dev/null +++ b/java8/src/main/java/io/cucumber/java8/Java8BackendProviderService.java @@ -0,0 +1,16 @@ +package io.cucumber.java8; + +import io.cucumber.core.backend.Backend; +import io.cucumber.core.backend.BackendProviderService; +import io.cucumber.core.backend.Container; +import io.cucumber.core.backend.Lookup; +import io.cucumber.core.io.ResourceLoader; +import io.cucumber.core.stepexpression.TypeRegistry; + +public final class Java8BackendProviderService implements BackendProviderService { + + @Override + public Backend create(Lookup lookup, Container container, ResourceLoader resourceLoader) { + return new Java8Backend(lookup, container, resourceLoader); + } +} diff --git a/java8/src/main/java/io/cucumber/java8/Java8HookDefinition.java b/java8/src/main/java/io/cucumber/java8/Java8HookDefinition.java new file mode 100644 index 0000000000..f7ad9730e7 --- /dev/null +++ b/java8/src/main/java/io/cucumber/java8/Java8HookDefinition.java @@ -0,0 +1,52 @@ +package io.cucumber.java8; + +import gherkin.pickles.PickleTag; +import io.cucumber.core.api.Scenario; +import io.cucumber.core.backend.HookDefinition; +import io.cucumber.core.filter.TagPredicate; +import io.cucumber.core.runtime.Invoker; + +import java.util.Collection; + +final class Java8HookDefinition extends AbstractGlueDefinition implements HookDefinition { + private final TagPredicate tagPredicate; + private final int order; + private final long timeoutMillis; + + private Java8HookDefinition(String tagExpressions, int order, long timeoutMillis, Object body) { + super(body, new Exception().getStackTrace()[3]); + this.order = order; + this.timeoutMillis = timeoutMillis; + this.tagPredicate = new TagPredicate(tagExpressions); + } + + Java8HookDefinition(String tagExpressions, int order, long timeoutMillis, HookBody hookBody) { + this(tagExpressions, order, timeoutMillis, (Object) hookBody); + } + + Java8HookDefinition(String tagExpressions, int order, long timeoutMillis, HookNoArgsBody hookNoArgsBody) { + this(tagExpressions, order, timeoutMillis, (Object) hookNoArgsBody); + } + + @Override + public void execute(final Scenario scenario) throws Throwable { + Object[] args; + if (method.getParameterCount() == 0) { + args = new Object[0]; + } else { + args = new Object[]{scenario}; + } + Invoker.invoke(body, method, timeoutMillis, args); + } + + @Override + public boolean matches(Collection tags) { + return tagPredicate.apply(tags); + } + + @Override + public int getOrder() { + return order; + } + +} diff --git a/java8/src/main/java/io/cucumber/java8/Java8ParameterInfo.java b/java8/src/main/java/io/cucumber/java8/Java8ParameterInfo.java new file mode 100644 index 0000000000..2a9607b31b --- /dev/null +++ b/java8/src/main/java/io/cucumber/java8/Java8ParameterInfo.java @@ -0,0 +1,35 @@ +package io.cucumber.java8; + +import io.cucumber.core.backend.ParameterInfo; +import io.cucumber.core.backend.TypeResolver; + +import java.lang.reflect.Type; + +final class Java8ParameterInfo implements ParameterInfo { + private final Type type; + private final TypeResolver typeResolver; + + Java8ParameterInfo(Type type, TypeResolver typeResolver) { + this.type = type; + this.typeResolver = typeResolver; + } + + public Type getType() { + return type; + } + + @Override + public boolean isTransposed() { + return false; + } + + @Override + public TypeResolver getTypeResolver() { + return typeResolver; + } + + @Override + public String toString() { + return type.toString(); + } +} diff --git a/java8/src/main/java/io/cucumber/java8/Java8Snippet.java b/java8/src/main/java/io/cucumber/java8/Java8Snippet.java new file mode 100644 index 0000000000..0b623f1f2b --- /dev/null +++ b/java8/src/main/java/io/cucumber/java8/Java8Snippet.java @@ -0,0 +1,15 @@ +package io.cucumber.java8; + +import java.text.MessageFormat; + +final class Java8Snippet extends AbstractJavaSnippet { + + @Override + public MessageFormat template() { + return new MessageFormat("" + + "{0}(\"{1}\", ({3}) -> '{'\n" + + " // {4}\n" + + "{5} throw new " + PendingException.class.getName() + "();\n" + + "'}');\n"); + } +} diff --git a/java8/src/main/java/io/cucumber/java8/Java8StepDefinition.java b/java8/src/main/java/io/cucumber/java8/Java8StepDefinition.java new file mode 100644 index 0000000000..3c7055053e --- /dev/null +++ b/java8/src/main/java/io/cucumber/java8/Java8StepDefinition.java @@ -0,0 +1,63 @@ +package io.cucumber.java8; + +import io.cucumber.core.backend.ParameterInfo; +import io.cucumber.core.backend.StepDefinition; +import io.cucumber.core.runtime.Invoker; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +import static net.jodah.typetools.TypeResolver.resolveRawArguments; + +final class Java8StepDefinition extends AbstractGlueDefinition implements StepDefinition { + + public static Java8StepDefinition create( + String expression, Class bodyClass, T body) { + return new Java8StepDefinition(expression, 0, bodyClass, body); + } + + public static StepDefinition create( + String expression, long timeoutMillis, Class bodyClass, T body) { + return new Java8StepDefinition(expression, timeoutMillis, bodyClass, body); + } + + private final long timeoutMillis; + private final List parameterInfos; + private final String expression; + + private Java8StepDefinition(String expression, + long timeoutMillis, + Class bodyClass, + T body) { + super(body, new Exception().getStackTrace()[3]); + this.timeoutMillis = timeoutMillis; + this.expression = expression; + this.parameterInfos = fromTypes(expression, location, resolveRawArguments(bodyClass, body.getClass())); + } + + @Override + public void execute(final Object[] args) throws Throwable { + Invoker.invoke(body, method, timeoutMillis, args); + } + + @Override + public List parameterInfos() { + return parameterInfos; + } + + @Override + public String getPattern() { + return expression; + } + + private static List fromTypes(String expression, StackTraceElement location, Type[] genericParameterTypes) { + List result = new ArrayList<>(); + for (Type type : genericParameterTypes) { + LambdaTypeResolver typeResolver = new LambdaTypeResolver(type, expression, location); + result.add(new Java8ParameterInfo(type, typeResolver)); + } + return result; + } + +} diff --git a/java8/src/main/java/io/cucumber/java8/LambdaGlue.java b/java8/src/main/java/io/cucumber/java8/LambdaGlue.java index da226b670f..90a92b908e 100644 --- a/java8/src/main/java/io/cucumber/java8/LambdaGlue.java +++ b/java8/src/main/java/io/cucumber/java8/LambdaGlue.java @@ -1,12 +1,9 @@ package io.cucumber.java8; -import cucumber.api.java8.GlueBase; -import cucumber.runtime.java.JavaBackend; -import cucumber.runtime.java8.Java8HookDefinition; import org.apiguardian.api.API; @API(status = API.Status.STABLE) -public interface LambdaGlue extends GlueBase { +public interface LambdaGlue { String EMPTY_TAG_EXPRESSIONS = ""; long NO_TIMEOUT = 0; @@ -16,40 +13,40 @@ public interface LambdaGlue extends GlueBase { /** * Defines an before hook. * - * @param body lambda to execute, takes {@link cucumber.api.Scenario} as an argument + * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void Before(final HookBody body) { - JavaBackend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); } /** * Defines an before hook. * * @param tagExpression a tag expression, if the expression applies to the current scenario this hook will be executed - * @param body lambda to execute, takes {@link cucumber.api.Scenario} as an argument + * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void Before(String tagExpression, final HookBody body) { - JavaBackend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); } /** * Defines an before hook. * * @param timeoutMillis max amount of milliseconds this is allowed to run for - * @param body lambda to execute, takes {@link cucumber.api.Scenario} as an argument + * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void Before(long timeoutMillis, final HookBody body) { - JavaBackend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, timeoutMillis, body)); } /** * Defines an before hook. * * @param order the order in which this hook should run. Higher numbers are run first - * @param body lambda to execute, takes {@link cucumber.api.Scenario} as an argument + * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void Before(int order, final HookBody body) { - JavaBackend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); } /** @@ -58,19 +55,19 @@ default void Before(int order, final HookBody body) { * @param tagExpression a tag expression, if the expression applies to the current scenario this hook will be executed * @param timeoutMillis max amount of milliseconds this is allowed to run for * @param order the order in which this hook should run. Higher numbers are run first - * @param body lambda to execute, takes {@link cucumber.api.Scenario} as an argument + * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void Before(String tagExpression, long timeoutMillis, int order, final HookBody body) { - JavaBackend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); } /** * Defines an before hook. * - * @param body lambda to execute, takes {@link cucumber.api.Scenario} as an argument + * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void Before(final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); } /** @@ -80,7 +77,7 @@ default void Before(final HookNoArgsBody body) { * @param body lambda to execute */ default void Before(String tagExpression, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); } /** @@ -90,7 +87,7 @@ default void Before(String tagExpression, final HookNoArgsBody body) { * @param body lambda to execute */ default void Before(long timeoutMillis, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, timeoutMillis, body)); } /** @@ -100,7 +97,7 @@ default void Before(long timeoutMillis, final HookNoArgsBody body) { * @param body lambda to execute */ default void Before(int order, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); } /** @@ -112,46 +109,46 @@ default void Before(int order, final HookNoArgsBody body) { * @param body lambda to execute */ default void Before(String tagExpression, long timeoutMillis, int order, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); } /** * Defines an before step hook. * - * @param body lambda to execute, takes {@link cucumber.api.Scenario} as an argument + * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void BeforeStep(final HookBody body) { - JavaBackend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); } /** * Defines an before step hook. * * @param tagExpression a tag expression, if the expression applies to the current scenario this hook will be executed - * @param body lambda to execute, takes {@link cucumber.api.Scenario} as an argument + * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void BeforeStep(String tagExpression, final HookBody body) { - JavaBackend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); } /** * Defines an before step hook. * * @param timeoutMillis max amount of milliseconds this is allowed to run for - * @param body lambda to execute, takes {@link cucumber.api.Scenario} as an argument + * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void BeforeStep(long timeoutMillis, final HookBody body) { - JavaBackend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, timeoutMillis, body)); } /** * Defines an before step hook. * * @param order the order in which this hook should run. Higher numbers are run first - * @param body lambda to execute, takes {@link cucumber.api.Scenario} as an argument + * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void BeforeStep(int order, final HookBody body) { - JavaBackend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); } /** @@ -160,10 +157,10 @@ default void BeforeStep(int order, final HookBody body) { * @param tagExpression a tag expression, if the expression applies to the current scenario this hook will be executed * @param timeoutMillis max amount of milliseconds this is allowed to run for * @param order the order in which this hook should run. Higher numbers are run first - * @param body lambda to execute, takes {@link cucumber.api.Scenario} as an argument + * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void BeforeStep(String tagExpression, long timeoutMillis, int order, final HookBody body) { - JavaBackend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); } /** @@ -172,7 +169,7 @@ default void BeforeStep(String tagExpression, long timeoutMillis, int order, fin * @param body lambda to execute */ default void BeforeStep(final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); } @@ -183,7 +180,7 @@ default void BeforeStep(final HookNoArgsBody body) { * @param body lambda to execute */ default void BeforeStep(String tagExpression, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); } @@ -194,7 +191,7 @@ default void BeforeStep(String tagExpression, final HookNoArgsBody body) { * @param body lambda to execute */ default void BeforeStep(long timeoutMillis, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, timeoutMillis, body)); } /** @@ -204,7 +201,7 @@ default void BeforeStep(long timeoutMillis, final HookNoArgsBody body) { * @param body lambda to execute */ default void BeforeStep(int order, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); } /** @@ -216,46 +213,46 @@ default void BeforeStep(int order, final HookNoArgsBody body) { * @param body lambda to execute */ default void BeforeStep(String tagExpression, long timeoutMillis, int order, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); } /** * Defines an after hook. * - * @param body lambda to execute, takes {@link cucumber.api.Scenario} as an argument + * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void After(final HookBody body) { - JavaBackend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); } /** * Defines an after hook. * * @param tagExpression a tag expression, if the expression applies to the current scenario this hook will be executed - * @param body lambda to execute, takes {@link cucumber.api.Scenario} as an argument + * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void After(String tagExpression, final HookBody body) { - JavaBackend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); } /** * Defines an after hook. * * @param timeoutMillis max amount of milliseconds this is allowed to run for - * @param body lambda to execute, takes {@link cucumber.api.Scenario} as an argument + * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void After(long timeoutMillis, final HookBody body) { - JavaBackend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, timeoutMillis, body)); } /** * Defines an after hook. * * @param order the order in which this hook should run. Higher numbers are run first - * @param body lambda to execute, takes {@link cucumber.api.Scenario} as an argument + * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void After(int order, final HookBody body) { - JavaBackend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); } /** @@ -264,10 +261,10 @@ default void After(int order, final HookBody body) { * @param tagExpression a tag expression, if the expression applies to the current scenario this hook will be executed * @param timeoutMillis max amount of milliseconds this is allowed to run for * @param order the order in which this hook should run. Higher numbers are run first - * @param body lambda to execute, takes {@link cucumber.api.Scenario} as an argument + * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void After(String tagExpression, long timeoutMillis, int order, final HookBody body) { - JavaBackend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); } /** @@ -276,7 +273,7 @@ default void After(String tagExpression, long timeoutMillis, int order, final Ho * @param body lambda to execute */ default void After(final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); } /** @@ -286,7 +283,7 @@ default void After(final HookNoArgsBody body) { * @param body lambda to execute */ default void After(String tagExpression, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); } /** @@ -296,7 +293,7 @@ default void After(String tagExpression, final HookNoArgsBody body) { * @param body lambda to execute */ default void After(long timeoutMillis, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, timeoutMillis, body)); } /** @@ -306,7 +303,7 @@ default void After(long timeoutMillis, final HookNoArgsBody body) { * @param body lambda to execute */ default void After(int order, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); } /** @@ -318,46 +315,46 @@ default void After(int order, final HookNoArgsBody body) { * @param body lambda to execute */ default void After(String tagExpression, long timeoutMillis, int order, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); } /** * Defines and after step hook. * - * @param body lambda to execute, takes {@link cucumber.api.Scenario} as an argument + * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void AfterStep(final HookBody body) { - JavaBackend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); } /** * Defines and after step hook. * * @param tagExpression a tag expression, if the expression applies to the current scenario this hook will be executed - * @param body lambda to execute, takes {@link cucumber.api.Scenario} as an argument + * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void AfterStep(String tagExpression, final HookBody body) { - JavaBackend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); } /** * Defines and after step hook. * * @param timeoutMillis max amount of milliseconds this is allowed to run for - * @param body lambda to execute, takes {@link cucumber.api.Scenario} as an argument + * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void AfterStep(long timeoutMillis, final HookBody body) { - JavaBackend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, timeoutMillis, body)); } /** * Defines and after step hook. * * @param order the order in which this hook should run. Higher numbers are run first - * @param body lambda to execute, takes {@link cucumber.api.Scenario} as an argument + * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void AfterStep(int order, final HookBody body) { - JavaBackend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); } /** @@ -366,10 +363,10 @@ default void AfterStep(int order, final HookBody body) { * @param tagExpression a tag expression, if the expression applies to the current scenario this hook will be executed * @param timeoutMillis max amount of milliseconds this is allowed to run for * @param order the order in which this hook should run. Higher numbers are run first - * @param body lambda to execute, takes {@link cucumber.api.Scenario} as an argument + * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void AfterStep(String tagExpression, long timeoutMillis, int order, final HookBody body) { - JavaBackend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); } /** @@ -378,7 +375,7 @@ default void AfterStep(String tagExpression, long timeoutMillis, int order, fina * @param body lambda to execute */ default void AfterStep(final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); } /** @@ -388,7 +385,7 @@ default void AfterStep(final HookNoArgsBody body) { * @param body lambda to execute */ default void AfterStep(String tagExpression, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); } /** @@ -398,7 +395,7 @@ default void AfterStep(String tagExpression, final HookNoArgsBody body) { * @param body lambda to execute */ default void AfterStep(long timeoutMillis, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, timeoutMillis, body)); } /** @@ -408,7 +405,7 @@ default void AfterStep(long timeoutMillis, final HookNoArgsBody body) { * @param body lambda to execute */ default void AfterStep(int order, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); } /** @@ -420,7 +417,7 @@ default void AfterStep(int order, final HookNoArgsBody body) { * @param body lambda to execute */ default void AfterStep(String tagExpression, long timeoutMillis, int order, final HookNoArgsBody body) { - JavaBackend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); } } diff --git a/java8/src/main/java/io/cucumber/java8/LambdaGlueRegistry.java b/java8/src/main/java/io/cucumber/java8/LambdaGlueRegistry.java new file mode 100644 index 0000000000..121a7e5e89 --- /dev/null +++ b/java8/src/main/java/io/cucumber/java8/LambdaGlueRegistry.java @@ -0,0 +1,18 @@ +package io.cucumber.java8; + +import io.cucumber.core.backend.HookDefinition; +import io.cucumber.core.backend.StepDefinition; + +interface LambdaGlueRegistry { + ThreadLocal INSTANCE = new ThreadLocal<>(); + + void addStepDefinition(StepDefinition stepDefinition); + + void addBeforeStepHookDefinition(HookDefinition beforeStepHook); + + void addAfterStepHookDefinition(HookDefinition afterStepHook); + + void addBeforeHookDefinition(HookDefinition beforeHook); + + void addAfterHookDefinition(HookDefinition afterHook); +} diff --git a/java8/src/main/java/io/cucumber/java8/LambdaTypeResolver.java b/java8/src/main/java/io/cucumber/java8/LambdaTypeResolver.java new file mode 100644 index 0000000000..4af00cab52 --- /dev/null +++ b/java8/src/main/java/io/cucumber/java8/LambdaTypeResolver.java @@ -0,0 +1,51 @@ +package io.cucumber.java8; + +import io.cucumber.core.backend.TypeResolver; +import io.cucumber.core.exception.CucumberException; + +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; + +import static java.lang.String.format; + +final class LambdaTypeResolver implements TypeResolver { + private final Type type; + private final String expression; + private final StackTraceElement location; + + LambdaTypeResolver(Type type, String expression, StackTraceElement location) { + this.type = type; + this.expression = expression; + this.location = location; + } + + @Override + public Type resolve() { + if (net.jodah.typetools.TypeResolver.Unknown.class.equals(type)) { + return Object.class; + } + return requireNonMapOrListType(type); + } + + private Type requireNonMapOrListType(Type argumentType) { + if (argumentType instanceof Class) { + Class argumentClass = (Class) argumentType; + if (List.class.isAssignableFrom(argumentClass) || Map.class.isAssignableFrom(argumentClass)) { + throw withLocation( + new CucumberException( + format("Can't use %s in lambda step definition \"%s\". " + + "Declare a DataTable argument instead and convert " + + "manually with asList/asLists/asMap/asMaps", + argumentClass.getName(), expression))); + } + } + return argumentType; + } + + private CucumberException withLocation(CucumberException exception) { + exception.setStackTrace(new StackTraceElement[]{location}); + return exception; + } + +} diff --git a/java8/src/main/java/io/cucumber/java8/PendingException.java b/java8/src/main/java/io/cucumber/java8/PendingException.java new file mode 100644 index 0000000000..220e9dd9e8 --- /dev/null +++ b/java8/src/main/java/io/cucumber/java8/PendingException.java @@ -0,0 +1,21 @@ +package io.cucumber.java8; + +import io.cucumber.core.backend.Pending; +import org.apiguardian.api.API; + +/** + * When thrown from a step marks it as not yet implemented. + * + * @see Java8Snippet + */ +@Pending +@API(status = API.Status.STABLE) +public final class PendingException extends RuntimeException { + public PendingException() { + this("TODO: implement me"); + } + + public PendingException(String message) { + super(message); + } +} diff --git a/java8/src/main/java/io/cucumber/java8/StepdefBody.java b/java8/src/main/java/io/cucumber/java8/StepdefBody.java index 32c5eee886..0eafc7b67d 100644 --- a/java8/src/main/java/io/cucumber/java8/StepdefBody.java +++ b/java8/src/main/java/io/cucumber/java8/StepdefBody.java @@ -3,7 +3,7 @@ import org.apiguardian.api.API; @API(status = API.Status.STABLE) -public interface StepdefBody extends cucumber.api.java8.StepdefBody { +public interface StepdefBody { @FunctionalInterface interface A0 extends StepdefBody { void accept() throws Throwable; diff --git a/java8/src/main/resources/META-INF/services/io.cucumber.core.backend.BackendProviderService b/java8/src/main/resources/META-INF/services/io.cucumber.core.backend.BackendProviderService new file mode 100644 index 0000000000..89144c1d84 --- /dev/null +++ b/java8/src/main/resources/META-INF/services/io.cucumber.core.backend.BackendProviderService @@ -0,0 +1 @@ +io.cucumber.java8.Java8BackendProviderService \ No newline at end of file diff --git a/java8/src/test/java/cucumber/runtime/java8/Java8LambdaStepDefinitionTest.java b/java8/src/test/java/cucumber/runtime/java8/Java8LambdaStepDefinitionTest.java deleted file mode 100644 index 646023c16a..0000000000 --- a/java8/src/test/java/cucumber/runtime/java8/Java8LambdaStepDefinitionTest.java +++ /dev/null @@ -1,88 +0,0 @@ -package cucumber.runtime.java8; - -import static java.util.Collections.emptyList; -import static java.util.Collections.singleton; -import static java.util.Collections.singletonList; -import static org.junit.Assert.assertEquals; - -import gherkin.ast.TableCell; -import gherkin.ast.TableRow; -import gherkin.pickles.PickleCell; -import gherkin.pickles.PickleRow; -import gherkin.pickles.PickleStep; -import gherkin.pickles.PickleString; -import gherkin.pickles.PickleTable; -import io.cucumber.datatable.DataTable; -import io.cucumber.stepexpression.Argument; -import io.cucumber.stepexpression.TypeRegistry; -import cucumber.api.java8.StepdefBody; -import cucumber.runtime.CucumberException; -import org.junit.Test; - -import java.util.Collections; -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() { - StepdefBody.A1 body = p1 -> { - }; - 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() { - StepdefBody.A2 body = (p1, p2) -> { - }; - Java8StepDefinition def = Java8StepDefinition.create("I have some step", StepdefBody.A2.class, body, typeRegistry); - assertEquals(Integer.valueOf(2), def.getParameterCount()); - } - - @Test - public void should_apply_identity_transform_to_doc_string_when_target_type_is_object() { - StepdefBody.A1 body = (p1) -> { - }; - Java8StepDefinition def = Java8StepDefinition.create("I have some step", StepdefBody.A1.class, body, typeRegistry); - PickleString pickleString = new PickleString(null, "content", "text"); - List arguments = def.matchedArguments(new PickleStep("I have some step", singletonList(pickleString), emptyList())); - assertEquals("content", arguments.get(0).getValue()); - } - - @Test - public void should_apply_identity_transform_to_data_table_when_target_type_is_object() { - StepdefBody.A1 body = (p1) -> { - }; - Java8StepDefinition def = Java8StepDefinition.create("I have some step", StepdefBody.A1.class, body, typeRegistry); - PickleTable table = new PickleTable(singletonList(new PickleRow(singletonList(new PickleCell(null, "content"))))); - List arguments = def.matchedArguments(new PickleStep("I have some step", singletonList(table), emptyList())); - assertEquals(DataTable.create(singletonList(singletonList("content"))), arguments.get(0).getValue()); - } - - - @Test - public void should_fail_for_param_with_non_generic_list() { - try { - StepdefBody.A1 body = p1 -> { - }; - Java8StepDefinition.create("I have some step", StepdefBody.A1.class, body, typeRegistry); - } catch (CucumberException expected) { - assertEquals("Can't use java.util.List in lambda step definition. Declare a DataTable argument instead and convert manually with asList/asLists/asMap/asMaps", expected.getMessage()); - } - } - - @Test - public void should_fail_for_param_with_generic_list() { - try { - StepdefBody.A1> body = p1 -> { - }; - Java8StepDefinition.create("I have some step", StepdefBody.A1.class, body, typeRegistry); - } catch (CucumberException expected) { - assertEquals("Can't use java.util.List in lambda step definition. Declare a DataTable argument instead and convert manually with asList/asLists/asMap/asMaps", expected.getMessage()); - } - } -} diff --git a/java8/src/test/java/cucumber/runtime/java8/SomeLambdaStepDefs.java b/java8/src/test/java/cucumber/runtime/java8/SomeLambdaStepDefs.java deleted file mode 100644 index 4332765c17..0000000000 --- a/java8/src/test/java/cucumber/runtime/java8/SomeLambdaStepDefs.java +++ /dev/null @@ -1,13 +0,0 @@ -package cucumber.runtime.java8; - -import cucumber.api.java8.En; - -final class SomeLambdaStepDefs implements En { - - SomeLambdaStepDefs() { - Given("I have a some step definition", () -> { - throw new Exception(); - }); - } - -} diff --git a/java8/src/test/java/cucumber/runtime/java8/test/AnonInnerClassStepdefs.java b/java8/src/test/java/cucumber/runtime/java8/test/AnonInnerClassStepdefs.java deleted file mode 100644 index d302f2c90d..0000000000 --- a/java8/src/test/java/cucumber/runtime/java8/test/AnonInnerClassStepdefs.java +++ /dev/null @@ -1,31 +0,0 @@ -package cucumber.runtime.java8.test; - -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 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/io/cucumber/java8/AnonInnerClassStepdefs.java b/java8/src/test/java/io/cucumber/java8/AnonInnerClassStepdefs.java new file mode 100644 index 0000000000..e604320659 --- /dev/null +++ b/java8/src/test/java/io/cucumber/java8/AnonInnerClassStepdefs.java @@ -0,0 +1,19 @@ +package io.cucumber.java8; + +import static org.junit.Assert.assertEquals; + +public class AnonInnerClassStepdefs implements LambdaGlue { + + public AnonInnerClassStepdefs() { + LambdaGlueRegistry.INSTANCE.get().addStepDefinition( + Java8StepDefinition.create( + "I have {int} java7 beans in my {word}", StepdefBody.A2.class, + new StepdefBody.A2() { + @Override + public void accept(Integer cukes, String what) throws Throwable { + assertEquals(42, cukes.intValue()); + assertEquals("belly", what); + } + })); + } +} diff --git a/java8/src/test/java/cucumber/runtime/java8/Java8AnonInnerClassStepDefinitionTest.java b/java8/src/test/java/io/cucumber/java8/Java8AnonInnerClassStepDefinitionTest.java similarity index 77% rename from java8/src/test/java/cucumber/runtime/java8/Java8AnonInnerClassStepDefinitionTest.java rename to java8/src/test/java/io/cucumber/java8/Java8AnonInnerClassStepDefinitionTest.java index 0a38d0c0fb..26ccb14129 100644 --- a/java8/src/test/java/cucumber/runtime/java8/Java8AnonInnerClassStepDefinitionTest.java +++ b/java8/src/test/java/io/cucumber/java8/Java8AnonInnerClassStepDefinitionTest.java @@ -1,28 +1,23 @@ -package cucumber.runtime.java8; +package io.cucumber.java8; -import static org.junit.Assert.assertEquals; - -import io.cucumber.stepexpression.TypeRegistry; -import cucumber.api.java8.StepdefBody; import org.junit.Test; import java.util.List; -import java.util.Locale; -public class Java8AnonInnerClassStepDefinitionTest { +import static org.junit.Assert.assertEquals; - private final TypeRegistry typeRegistry = new TypeRegistry(Locale.ENGLISH); +public class Java8AnonInnerClassStepDefinitionTest { @Test public void should_calculate_parameters_count_from_body_with_one_param() { - Java8StepDefinition java8StepDefinition = Java8StepDefinition.create("I have some step", StepdefBody.A1.class, oneParamStep(), typeRegistry); - assertEquals(Integer.valueOf(1), java8StepDefinition.getParameterCount()); + Java8StepDefinition java8StepDefinition = Java8StepDefinition.create("I have some step", StepdefBody.A1.class, oneParamStep()); + assertEquals(1, java8StepDefinition.parameterInfos().size()); } @Test public void should_calculate_parameters_count_from_body_with_two_params() { - Java8StepDefinition java8StepDefinition = Java8StepDefinition.create("I have some step", StepdefBody.A2.class, twoParamStep(), typeRegistry); - assertEquals(Integer.valueOf(2), java8StepDefinition.getParameterCount()); + Java8StepDefinition java8StepDefinition = Java8StepDefinition.create("I have some step", StepdefBody.A2.class, twoParamStep()); + assertEquals(2, java8StepDefinition.parameterInfos().size()); } private StepdefBody.A1 oneParamStep() { diff --git a/java8/src/test/java/io/cucumber/java8/Java8BackendTest.java b/java8/src/test/java/io/cucumber/java8/Java8BackendTest.java new file mode 100644 index 0000000000..547d047411 --- /dev/null +++ b/java8/src/test/java/io/cucumber/java8/Java8BackendTest.java @@ -0,0 +1,48 @@ +package io.cucumber.java8; + +import io.cucumber.core.backend.Glue; +import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.io.MultiLoader; +import io.cucumber.core.io.ResourceLoader; +import io.cucumber.java8.steps.Stepdefs; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.net.URI; + +import static java.lang.Thread.currentThread; +import static java.util.Arrays.asList; +import static org.mockito.Mockito.verify; + +public class Java8BackendTest { + + @Rule + public MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock + private Glue glue; + + @Mock + private ObjectFactory factory; + + private Java8Backend backend; + + @Before + public void createBackend() { + ClassLoader classLoader = currentThread().getContextClassLoader(); + ResourceLoader resourceLoader = new MultiLoader(classLoader); + this.backend = new Java8Backend(factory, factory, resourceLoader); + } + + @Test + public void finds_step_definitions_by_classpath_url() { + backend.loadGlue(glue, asList(URI.create("classpath:io/cucumber/java8/steps"))); + backend.buildWorld(); + verify(factory).addClass(Stepdefs.class); + } + +} diff --git a/java8/src/test/java/cucumber/runtime/java8/Java8LambdaStepDefinitionMarksCorrectStackElementTest.java b/java8/src/test/java/io/cucumber/java8/Java8LambdaStepDefinitionMarksCorrectStackElementTest.java similarity index 80% rename from java8/src/test/java/cucumber/runtime/java8/Java8LambdaStepDefinitionMarksCorrectStackElementTest.java rename to java8/src/test/java/io/cucumber/java8/Java8LambdaStepDefinitionMarksCorrectStackElementTest.java index 63e79a1cb4..39263936c5 100644 --- a/java8/src/test/java/cucumber/runtime/java8/Java8LambdaStepDefinitionMarksCorrectStackElementTest.java +++ b/java8/src/test/java/io/cucumber/java8/Java8LambdaStepDefinitionMarksCorrectStackElementTest.java @@ -1,17 +1,12 @@ -package cucumber.runtime.java8; +package io.cucumber.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 io.cucumber.core.backend.HookDefinition; +import io.cucumber.core.backend.StepDefinition; 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 @@ -47,8 +42,8 @@ private class MyLambdaGlueRegistry implements LambdaGlueRegistry { private StepDefinition stepDefinition; @Override - public void addStepDefinition(Function stepDefinitionFunction) { - stepDefinition = stepDefinitionFunction.apply(new TypeRegistry(ENGLISH)); + public void addStepDefinition(StepDefinition stepDefinition) { + this.stepDefinition = stepDefinition; } @Override @@ -75,4 +70,14 @@ StepDefinition getStepDefinition() { return stepDefinition; } } + + static final class SomeLambdaStepDefs implements En { + + SomeLambdaStepDefs() { + Given("I have a some step definition", () -> { + throw new Exception(); + }); + } + + } } diff --git a/java8/src/test/java/io/cucumber/java8/Java8LambdaStepDefinitionTest.java b/java8/src/test/java/io/cucumber/java8/Java8LambdaStepDefinitionTest.java new file mode 100644 index 0000000000..ea62a54e0a --- /dev/null +++ b/java8/src/test/java/io/cucumber/java8/Java8LambdaStepDefinitionTest.java @@ -0,0 +1,60 @@ +package io.cucumber.java8; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public class Java8LambdaStepDefinitionTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void should_calculate_parameters_count_from_body_with_one_param() { + StepdefBody.A1 body = p1 -> { + }; + Java8StepDefinition stepDefinition = Java8StepDefinition.create("some step", StepdefBody.A1.class, body); + assertEquals(1, stepDefinition.parameterInfos().size()); + } + + @Test + public void should_calculate_parameters_count_from_body_with_two_params() { + StepdefBody.A2 body = (p1, p2) -> { + }; + Java8StepDefinition stepDefinition = Java8StepDefinition.create("some step", StepdefBody.A2.class, body); + assertEquals(2, stepDefinition.parameterInfos().size()); + } + + @Test + public void should_resolve_type_to_object() { + StepdefBody.A1 body = (p1) -> { + }; + Java8StepDefinition stepDefinition = Java8StepDefinition.create("some step", StepdefBody.A1.class, body); + + assertEquals(Object.class, stepDefinition.parameterInfos().get(0).getType()); + } + + @Test + public void should_fail_for_param_with_non_generic_list() { + expectedException.expectMessage("Can't use java.util.List in lambda step definition \"some step\". Declare a DataTable argument instead and convert manually with asList/asLists/asMap/asMaps"); + + StepdefBody.A1 body = p1 -> { + }; + Java8StepDefinition stepDefinition = Java8StepDefinition.create("some step", StepdefBody.A1.class, body); + stepDefinition.parameterInfos().get(0).getTypeResolver().resolve(); + } + + @Test + public void should_fail_for_param_with_generic_list() { + expectedException.expectMessage("Can't use java.util.List in lambda step definition \"some step\". Declare a DataTable argument instead and convert manually with asList/asLists/asMap/asMaps"); + + StepdefBody.A1> body = p1 -> { + }; + Java8StepDefinition stepDefinition = Java8StepDefinition.create("some step", StepdefBody.A1.class, body); + stepDefinition.parameterInfos().get(0).getTypeResolver().resolve(); + } +} diff --git a/java/src/test/java/cucumber/runtime/java/Java8SnippetTest.java b/java8/src/test/java/io/cucumber/java8/Java8SnippetTest.java similarity index 54% rename from java/src/test/java/cucumber/runtime/java/Java8SnippetTest.java rename to java8/src/test/java/io/cucumber/java8/Java8SnippetTest.java index 6ae8215d72..1042370027 100644 --- a/java/src/test/java/cucumber/runtime/java/Java8SnippetTest.java +++ b/java8/src/test/java/io/cucumber/java8/Java8SnippetTest.java @@ -1,14 +1,14 @@ -package cucumber.runtime.java; +package io.cucumber.java8; -import cucumber.runtime.snippets.SnippetGenerator; import gherkin.pickles.Argument; import gherkin.pickles.PickleLocation; import gherkin.pickles.PickleStep; +import io.cucumber.core.snippets.SnippetGenerator; +import io.cucumber.core.snippets.SnippetType; import io.cucumber.cucumberexpressions.ParameterTypeRegistry; import org.junit.Test; import java.util.Collections; -import java.util.List; import java.util.Locale; import static org.junit.Assert.assertEquals; @@ -19,17 +19,21 @@ public class Java8SnippetTest { @Test public void generatesPlainSnippet() { String expected = "" + - "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 cucumber.api.PendingException();\n" + - "});\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 io.cucumber.java8.PendingException();\n" + + "});\n"; System.out.println(expected); assertEquals(expected, snippetFor("I have 4 cukes in my \"big\" belly")); } private String snippetFor(String name) { PickleStep step = new PickleStep(name, Collections.emptyList(), Collections.emptyList()); - List snippet = new SnippetGenerator(new Java8Snippet(), new ParameterTypeRegistry(Locale.ENGLISH)).getSnippet(step, GIVEN_KEYWORD, null); - return StringJoiner.join("\n", snippet); + return String.join( + "\n", + new SnippetGenerator( + new Java8Snippet(), + new ParameterTypeRegistry(Locale.ENGLISH)).getSnippet(step, GIVEN_KEYWORD, SnippetType.UNDERSCORE) + ); } } \ No newline at end of file diff --git a/java8/src/test/java/cucumber/runtime/java8/test/LambdaStepdefs.java b/java8/src/test/java/io/cucumber/java8/LambdaStepdefs.java similarity index 94% rename from java8/src/test/java/cucumber/runtime/java8/test/LambdaStepdefs.java rename to java8/src/test/java/io/cucumber/java8/LambdaStepdefs.java index e702d80b28..543150d58e 100644 --- a/java8/src/test/java/cucumber/runtime/java8/test/LambdaStepdefs.java +++ b/java8/src/test/java/io/cucumber/java8/LambdaStepdefs.java @@ -1,17 +1,16 @@ -package cucumber.runtime.java8.test; +package io.cucumber.java8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; -import cucumber.api.Scenario; +import io.cucumber.core.api.Scenario; import io.cucumber.datatable.DataTable; -import cucumber.api.java8.En; import java.util.List; -public class LambdaStepdefs implements En { +public class LambdaStepdefs implements io.cucumber.java8.En { private static LambdaStepdefs lastInstance; private final int outside = 41; @@ -73,7 +72,7 @@ public LambdaStepdefs() { assertEquals(42, outside + 1); }); - Given("I will give you {int} and {float} and {string} and {int}", (Integer a, Float b, String c, + Given("I will give you {int} and {float} and {word} and {int}", (Integer a, Float b, String c, Integer d) -> { assertEquals((Integer) 1, a); diff --git a/java/src/test/java/cucumber/runtime/java/test/RunCucumberTest.java b/java8/src/test/java/io/cucumber/java8/RunCucumberTest.java similarity index 78% rename from java/src/test/java/cucumber/runtime/java/test/RunCucumberTest.java rename to java8/src/test/java/io/cucumber/java8/RunCucumberTest.java index 46a6c6c86d..836469dd6a 100644 --- a/java/src/test/java/cucumber/runtime/java/test/RunCucumberTest.java +++ b/java8/src/test/java/io/cucumber/java8/RunCucumberTest.java @@ -1,4 +1,4 @@ -package cucumber.runtime.java.test; +package io.cucumber.java8; import io.cucumber.junit.Cucumber; import org.junit.runner.RunWith; diff --git a/java8/src/test/java/io/cucumber/java8/SingletonFactory.java b/java8/src/test/java/io/cucumber/java8/SingletonFactory.java new file mode 100644 index 0000000000..bc7c152264 --- /dev/null +++ b/java8/src/test/java/io/cucumber/java8/SingletonFactory.java @@ -0,0 +1,40 @@ +package io.cucumber.java8; + +import io.cucumber.core.backend.ObjectFactory; + +class SingletonFactory implements ObjectFactory { + private Object singleton; + + public SingletonFactory() { + this(null); + } + + public SingletonFactory(Object singleton) { + this.singleton = singleton; + } + + @Override + public void start() { + } + + @Override + public void stop() { + } + + @Override + public boolean addClass(Class clazz) { + return true; + } + + @Override + public T getInstance(Class type) { + if (singleton == null) { + throw new IllegalStateException("No object is set"); + } + return type.cast(singleton); + } + + public void setInstance(Object o) { + singleton = o; + } +} diff --git a/java8/src/test/java/cucumber/runtime/java8/test/TypeRegistryConfiguration.java b/java8/src/test/java/io/cucumber/java8/TypeRegistryConfiguration.java similarity index 71% rename from java8/src/test/java/cucumber/runtime/java8/test/TypeRegistryConfiguration.java rename to java8/src/test/java/io/cucumber/java8/TypeRegistryConfiguration.java index 871f8669f3..16a15cdd7a 100644 --- a/java8/src/test/java/cucumber/runtime/java8/test/TypeRegistryConfiguration.java +++ b/java8/src/test/java/io/cucumber/java8/TypeRegistryConfiguration.java @@ -1,9 +1,8 @@ -package cucumber.runtime.java8.test; +package io.cucumber.java8; -import cucumber.api.TypeRegistryConfigurer; -import cucumber.api.TypeRegistry; +import io.cucumber.core.api.TypeRegistry; +import io.cucumber.core.api.TypeRegistryConfigurer; import io.cucumber.datatable.DataTableType; -import cucumber.runtime.java8.test.LambdaStepdefs.Person; import java.util.Locale; import java.util.Map; @@ -20,9 +19,9 @@ public Locale locale() { @Override public void configureTypeRegistry(TypeRegistry typeRegistry) { typeRegistry.defineDataTableType(new DataTableType( - Person.class, + LambdaStepdefs.Person.class, (Map map) -> { - Person person = new Person(); + LambdaStepdefs.Person person = new LambdaStepdefs.Person(); person.first = map.get("first"); person.last = map.get("last"); return person; diff --git a/java8/src/test/java/io/cucumber/java8/steps/Stepdefs.java b/java8/src/test/java/io/cucumber/java8/steps/Stepdefs.java new file mode 100644 index 0000000000..5802b1edca --- /dev/null +++ b/java8/src/test/java/io/cucumber/java8/steps/Stepdefs.java @@ -0,0 +1,15 @@ +package io.cucumber.java8.steps; + +import io.cucumber.java8.En; + +public class Stepdefs implements En { + + public Stepdefs() { + + Given("test", () -> { + + }); + + } + +} diff --git a/java8/src/test/resources/cucumber/runtime/java8/test/anon-inner-class-step-definitions.feature b/java8/src/test/resources/io/cucumber/java8/anon-inner-class-step-definitions.feature similarity index 100% rename from java8/src/test/resources/cucumber/runtime/java8/test/anon-inner-class-step-definitions.feature rename to java8/src/test/resources/io/cucumber/java8/anon-inner-class-step-definitions.feature diff --git a/java8/src/test/resources/cucumber/runtime/java8/test/lambda-step-definitions.feature b/java8/src/test/resources/io/cucumber/java8/lambda-step-definitions.feature similarity index 100% rename from java8/src/test/resources/cucumber/runtime/java8/test/lambda-step-definitions.feature rename to java8/src/test/resources/io/cucumber/java8/lambda-step-definitions.feature diff --git a/junit/README.md b/junit/README.md index a03994f8c8..417cc79dcc 100644 --- a/junit/README.md +++ b/junit/README.md @@ -1,9 +1,7 @@ Cucumber JUnit ============== -Use JUnit to execute cucumber scenarios. - -Add the `cucumber-junit` dependency to your pom.xml: +Use JUnit to execute cucumber scenarios. To use add the `cucumber-junit` dependency to your pom.xml: ```xml diff --git a/junit/pom.xml b/junit/pom.xml index fdfcb34ea8..59655f09d8 100644 --- a/junit/pom.xml +++ b/junit/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 4.7.2-SNAPSHOT + 5.0.0-SNAPSHOT cucumber-junit @@ -25,16 +25,21 @@ junit junit - - - org.mockito - mockito-core - test - - org.hamcrest hamcrest-library + + + org.junit.jupiter + junit-jupiter-api + + + org.junit.vintage + junit-vintage-engine + + + org.mockito + mockito-core test diff --git a/junit/src/main/java/cucumber/api/junit/Cucumber.java b/junit/src/main/java/cucumber/api/junit/Cucumber.java deleted file mode 100644 index 31347c65fb..0000000000 --- a/junit/src/main/java/cucumber/api/junit/Cucumber.java +++ /dev/null @@ -1,17 +0,0 @@ -package cucumber.api.junit; - -import org.apiguardian.api.API; -import org.junit.runners.model.InitializationError; - -/** - * @see io.cucumber.junit.Cucumber - * @deprecated use {@link io.cucumber.junit.Cucumber} instead. - */ -@Deprecated -@API(status = API.Status.MAINTAINED) -public class Cucumber extends io.cucumber.junit.Cucumber { - - public Cucumber(Class clazz) throws InitializationError { - super(clazz); - } -} diff --git a/junit/src/main/java/io/cucumber/junit/Assertions.java b/junit/src/main/java/io/cucumber/junit/Assertions.java index f6aa816150..a89f6142ca 100644 --- a/junit/src/main/java/io/cucumber/junit/Assertions.java +++ b/junit/src/main/java/io/cucumber/junit/Assertions.java @@ -1,6 +1,6 @@ package io.cucumber.junit; -import cucumber.runtime.CucumberException; +import io.cucumber.core.exception.CucumberException; import java.lang.annotation.Annotation; import java.lang.reflect.Method; diff --git a/junit/src/main/java/io/cucumber/junit/Cucumber.java b/junit/src/main/java/io/cucumber/junit/Cucumber.java index 534a2a9319..719e32eb22 100644 --- a/junit/src/main/java/io/cucumber/junit/Cucumber.java +++ b/junit/src/main/java/io/cucumber/junit/Cucumber.java @@ -1,28 +1,33 @@ package io.cucumber.junit; -import cucumber.api.StepDefinitionReporter; -import cucumber.api.event.TestRunFinished; -import cucumber.api.event.TestRunStarted; -import cucumber.runner.EventBus; -import cucumber.runner.ThreadLocalRunnerSupplier; -import cucumber.runner.TimeService; -import cucumber.runner.TimeServiceEventBus; -import cucumber.runtime.BackendModuleBackendSupplier; -import cucumber.runtime.BackendSupplier; -import cucumber.runtime.ClassFinder; -import cucumber.runtime.Env; -import io.cucumber.core.options.EnvironmentOptionsParser; -import cucumber.runtime.FeaturePathFeatureSupplier; -import io.cucumber.core.options.RuntimeOptions; +import io.cucumber.core.backend.ObjectFactoryServiceLoader; +import io.cucumber.core.event.TestSourceRead; +import io.cucumber.core.event.TestRunFinished; +import io.cucumber.core.event.TestRunStarted; +import io.cucumber.core.options.Constants; import io.cucumber.core.options.CucumberOptionsAnnotationParser; -import cucumber.runtime.filter.Filters; -import cucumber.runtime.formatter.PluginFactory; -import cucumber.runtime.formatter.Plugins; -import cucumber.runtime.io.MultiLoader; -import cucumber.runtime.io.ResourceLoader; -import cucumber.runtime.io.ResourceLoaderClassFinder; -import cucumber.runtime.model.CucumberFeature; -import cucumber.runtime.model.FeatureLoader; +import io.cucumber.core.options.CucumberProperties; +import io.cucumber.core.options.CucumberPropertiesParser; +import io.cucumber.core.options.RuntimeOptions; +import io.cucumber.core.runtime.ScanningTypeRegistryConfigurerSupplier; +import io.cucumber.core.runtime.ObjectFactorySupplier; +import io.cucumber.core.runtime.ThreadLocalObjectFactorySupplier; +import io.cucumber.core.eventbus.EventBus; +import io.cucumber.core.runtime.BackendServiceLoader; +import io.cucumber.core.runtime.BackendSupplier; +import io.cucumber.core.runtime.TimeServiceEventBus; +import io.cucumber.core.io.ClassFinder; +import io.cucumber.core.runtime.FeaturePathFeatureSupplier; +import io.cucumber.core.filter.Filters; +import io.cucumber.core.plugin.Plugins; +import io.cucumber.core.plugin.PluginFactory; +import io.cucumber.core.feature.FeatureLoader; +import io.cucumber.core.runtime.ThreadLocalRunnerSupplier; +import io.cucumber.core.io.MultiLoader; +import io.cucumber.core.io.ResourceLoader; +import io.cucumber.core.io.ResourceLoaderClassFinder; +import io.cucumber.core.feature.CucumberFeature; +import io.cucumber.core.runtime.TypeRegistryConfigurerSupplier; import org.apiguardian.api.API; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -34,12 +39,14 @@ import org.junit.runners.model.RunnerScheduler; import org.junit.runners.model.Statement; +import java.time.Clock; import java.util.ArrayList; import java.util.List; /** + * Cucumber JUnit Runner. *

- * Classes annotated with {@code @RunWith(Cucumber.class)} will run a Cucumber Feature. + * A class annotated with {@code @RunWith(Cucumber.class)} will run feature files as junit tests. * In general, the runner class should be empty without any fields or methods. * For example: *

@@ -53,7 +60,13 @@
  * path as the annotated class. For example, if the annotated class is {@code com.example.RunCucumber} then
  * features and glue are assumed to be located in {@code com.example}.
  * 

- * Additional hints can be provided to Cucumber by annotating the class with {@link CucumberOptions}. + * Options can be provided in by (order of precedence): + *

    + *
  1. Setting {@value Constants#CUCUMBER_OPTIONS_PROPERTY_NAME} property in {@link System#getProperties()} ()}
  2. + *
  3. Setting {@value Constants#CUCUMBER_OPTIONS_PROPERTY_NAME} property in {@link System#getenv()}
  4. + *
  5. Annotating the runner class with {@link CucumberOptions}
  6. + *
  7. Setting {@value Constants#CUCUMBER_OPTIONS_PROPERTY_NAME} property in {@code cucumber.properties}
  8. + *
*

* Cucumber also supports JUnits {@link ClassRule}, {@link BeforeClass} and {@link AfterClass} annotations. * These will be executed before and after all scenarios. Using these is not recommended as it limits the portability @@ -63,10 +76,9 @@ * @see CucumberOptions */ @API(status = API.Status.STABLE) -public class Cucumber extends ParentRunner { +public final class Cucumber extends ParentRunner { private final List children = new ArrayList<>(); private final EventBus bus; - private final ThreadLocalRunnerSupplier runnerSupplier; private final List features; private final Plugins plugins; @@ -84,28 +96,43 @@ public Cucumber(Class clazz) throws InitializationError { ClassLoader classLoader = clazz.getClassLoader(); ResourceLoader resourceLoader = new MultiLoader(classLoader); + ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader); // Parse the options early to provide fast feedback about invalid options + RuntimeOptions propertiesFileOptions = new CucumberPropertiesParser(resourceLoader) + .parse(CucumberProperties.fromPropertiesFile()) + .build(); + RuntimeOptions annotationOptions = new CucumberOptionsAnnotationParser(resourceLoader) .withOptionsProvider(new JUnitCucumberOptionsProvider()) .parse(clazz) - .build(); + .build(propertiesFileOptions); - RuntimeOptions runtimeOptions = new EnvironmentOptionsParser(resourceLoader) - .parse(Env.INSTANCE) + RuntimeOptions environmentOptions = new CucumberPropertiesParser(resourceLoader) + .parse(CucumberProperties.fromEnvironment()) .build(annotationOptions); + RuntimeOptions runtimeOptions = new CucumberPropertiesParser(resourceLoader) + .parse(CucumberProperties.fromSystemProperties()) + .build(environmentOptions); + + // Next parse the junit options + JUnitOptions junitPropertiesFileOptions = new JUnitOptionsParser() + .parse(CucumberProperties.fromPropertiesFile()) + .build(); + JUnitOptions junitAnnotationOptions = new JUnitOptionsParser() .parse(clazz) - .build(); + .build(junitPropertiesFileOptions); - JUnitOptions junitOptions = new JUnitOptionsParser() - .parse(runtimeOptions.getJunitOptions()) - .setStrict(runtimeOptions.isStrict()) + JUnitOptions junitEnvironmentOptions = new JUnitOptionsParser() + .parse(CucumberProperties.fromEnvironment()) .build(junitAnnotationOptions); - - ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader); + JUnitOptions junitOptions = new JUnitOptionsParser() + .parse(CucumberProperties.fromSystemProperties()) + .setStrict(runtimeOptions.isStrict()) + .build(junitEnvironmentOptions); // Parse the features early. Don't proceed when there are lexer errors FeatureLoader featureLoader = new FeatureLoader(resourceLoader); @@ -113,11 +140,14 @@ public Cucumber(Class clazz) throws InitializationError { this.features = featureSupplier.get(); // Create plugins after feature parsing to avoid the creation of empty files on lexer errors. - this.plugins = new Plugins(classLoader, new PluginFactory(), runtimeOptions); - this.bus = new TimeServiceEventBus(TimeService.SYSTEM); - - BackendSupplier backendSupplier = new BackendModuleBackendSupplier(resourceLoader, classFinder, runtimeOptions); - this.runnerSupplier = new ThreadLocalRunnerSupplier(runtimeOptions, bus, backendSupplier); + this.plugins = new Plugins(new PluginFactory(), runtimeOptions); + this.bus = new TimeServiceEventBus(Clock.systemUTC()); + + ObjectFactoryServiceLoader objectFactoryServiceLoader = new ObjectFactoryServiceLoader(runtimeOptions); + ObjectFactorySupplier objectFactorySupplier = new ThreadLocalObjectFactorySupplier(objectFactoryServiceLoader); + BackendSupplier backendSupplier = new BackendServiceLoader(resourceLoader, objectFactorySupplier); + TypeRegistryConfigurerSupplier typeRegistryConfigurerSupplier = new ScanningTypeRegistryConfigurerSupplier(classFinder, runtimeOptions); + ThreadLocalRunnerSupplier runnerSupplier = new ThreadLocalRunnerSupplier(runtimeOptions, bus, backendSupplier, objectFactorySupplier, typeRegistryConfigurerSupplier); Filters filters = new Filters(runtimeOptions); for (CucumberFeature cucumberFeature : features) { FeatureRunner featureRunner = new FeatureRunner(cucumberFeature, filters, runnerSupplier, junitOptions); @@ -128,7 +158,7 @@ public Cucumber(Class clazz) throws InitializationError { } @Override - public List getChildren() { + protected List getChildren() { return children; } @@ -163,14 +193,12 @@ public void evaluate() throws Throwable { plugins.setEventBusOnEventListenerPlugins(bus); } - bus.send(new TestRunStarted(bus.getTime(), bus.getTimeMillis())); + bus.send(new TestRunStarted(bus.getInstant())); for (CucumberFeature feature : features) { - feature.sendTestSourceRead(bus); + bus.send(new TestSourceRead(bus.getInstant(), feature.getUri().toString(), feature.getSource())); } - StepDefinitionReporter stepDefinitionReporter = plugins.stepDefinitionReporter(); - runnerSupplier.get().reportStepDefinitions(stepDefinitionReporter); runFeatures.evaluate(); - bus.send(new TestRunFinished(bus.getTime(), bus.getTimeMillis())); + bus.send(new TestRunFinished(bus.getInstant())); } } diff --git a/junit/src/main/java/io/cucumber/junit/CucumberOptions.java b/junit/src/main/java/io/cucumber/junit/CucumberOptions.java index bbcf11de7f..7cca11be67 100644 --- a/junit/src/main/java/io/cucumber/junit/CucumberOptions.java +++ b/junit/src/main/java/io/cucumber/junit/CucumberOptions.java @@ -1,6 +1,5 @@ package io.cucumber.junit; -import cucumber.api.Plugin; import org.apiguardian.api.API; import java.lang.annotation.ElementType; @@ -34,7 +33,7 @@ * class. For example, if the annotated class is {@code com.example.RunCucumber} * then features are assumed to be located in {@code classpath:com/example}. * - * @see io.cucumber.core.model.FeatureWithLines + * @see io.cucumber.core.feature.FeatureWithLines */ String[] features() default {}; @@ -46,7 +45,7 @@ * class. For example, if the annotated class is {@code com.example.RunCucumber} * then glue is assumed to be located in {@code com.example}. * - * @see io.cucumber.core.model.GluePath + * @see io.cucumber.core.feature.GluePath */ String[] glue() default {}; @@ -77,7 +76,7 @@ * Plugins can be provided with an argument. For example * {@code json:target/cucumber-report.json} * - * @see Plugin + * @see io.cucumber.core.plugin.Plugin */ String[] plugin() default {}; @@ -96,15 +95,6 @@ */ SnippetType snippets() default SnippetType.UNDERSCORE; - /** - * Pass options to the JUnit runner. - * - * @deprecated use {@link #useFileNameCompatibleName()} or - * {@link #stepNotifications()} instead. - */ - @Deprecated - String[] junit() default {}; - /** * Use filename compatible names. *

@@ -127,6 +117,16 @@ */ boolean stepNotifications() default false; + /** + * Specify a custom ObjectFactory. + *

+ * In case a custom ObjectFactory is needed, the class can be specified here. + * A custom ObjectFactory might be needed when more granular control is needed + * over the dependency injection mechanism. + */ + Class objectFactory() default NoObjectFactory.class; + + enum SnippetType { UNDERSCORE, CAMELCASE } diff --git a/junit/src/main/java/io/cucumber/junit/FeatureRunner.java b/junit/src/main/java/io/cucumber/junit/FeatureRunner.java index 28730c3f03..54ca24f9b2 100644 --- a/junit/src/main/java/io/cucumber/junit/FeatureRunner.java +++ b/junit/src/main/java/io/cucumber/junit/FeatureRunner.java @@ -1,13 +1,14 @@ package io.cucumber.junit; -import cucumber.runner.ThreadLocalRunnerSupplier; -import cucumber.runtime.CucumberException; -import cucumber.runtime.filter.Filters; -import cucumber.runtime.model.CucumberFeature; import gherkin.ast.Feature; import gherkin.events.PickleEvent; +import io.cucumber.core.exception.CucumberException; +import io.cucumber.core.feature.CucumberFeature; +import io.cucumber.core.filter.Filters; +import io.cucumber.core.runtime.RunnerSupplier; import io.cucumber.junit.PickleRunners.PickleRunner; import org.junit.runner.Description; +import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunNotifier; import org.junit.runners.ParentRunner; import org.junit.runners.model.InitializationError; @@ -26,7 +27,7 @@ final class FeatureRunner extends ParentRunner { private final CucumberFeature cucumberFeature; private Description description; - FeatureRunner(CucumberFeature cucumberFeature, Filters filters, ThreadLocalRunnerSupplier runnerSupplier, JUnitOptions jUnitOptions) throws InitializationError { + FeatureRunner(CucumberFeature cucumberFeature, Filters filters, RunnerSupplier runnerSupplier, JUnitOptions jUnitOptions) throws InitializationError { super(null); this.cucumberFeature = cucumberFeature; buildFeatureElementRunners(filters, runnerSupplier, jUnitOptions); @@ -49,7 +50,7 @@ public Description getDescription() { return description; } - public boolean isEmpty() { + boolean isEmpty() { return children.isEmpty(); } @@ -65,10 +66,18 @@ protected Description describeChild(PickleRunner child) { @Override protected void runChild(PickleRunner child, RunNotifier notifier) { - child.run(notifier); + notifier.fireTestStarted(getDescription()); + try { + child.run(notifier); + } catch (Throwable e) { + notifier.fireTestFailure(new Failure(getDescription(), e)); + notifier.pleaseStop(); + } finally { + notifier.fireTestFinished(getDescription()); + } } - private void buildFeatureElementRunners(Filters filters, ThreadLocalRunnerSupplier runnerSupplier, JUnitOptions jUnitOptions) { + private void buildFeatureElementRunners(Filters filters, RunnerSupplier runnerSupplier, JUnitOptions jUnitOptions) { for (PickleEvent pickleEvent : cucumberFeature.getPickles()) { if (filters.matchesFilters(pickleEvent)) { try { diff --git a/junit/src/main/java/io/cucumber/junit/JUnitCucumberOptionsProvider.java b/junit/src/main/java/io/cucumber/junit/JUnitCucumberOptionsProvider.java index 472ae8aa11..2772915a3c 100644 --- a/junit/src/main/java/io/cucumber/junit/JUnitCucumberOptionsProvider.java +++ b/junit/src/main/java/io/cucumber/junit/JUnitCucumberOptionsProvider.java @@ -1,6 +1,7 @@ package io.cucumber.junit; -import cucumber.api.SnippetType; +import io.cucumber.core.snippets.SnippetType; +import io.cucumber.core.backend.ObjectFactory; import io.cucumber.core.options.CucumberOptionsAnnotationParser; final class JUnitCucumberOptionsProvider implements CucumberOptionsAnnotationParser.OptionsProvider { @@ -68,18 +69,18 @@ public String[] name() { @Override public SnippetType snippets() { switch (annotation.snippets()) { - case UNDERSCORE: - return SnippetType.UNDERSCORE; - case CAMELCASE: - return SnippetType.CAMELCASE; - default: - throw new IllegalArgumentException("" + annotation.snippets()); + case UNDERSCORE: + return SnippetType.UNDERSCORE; + case CAMELCASE: + return SnippetType.CAMELCASE; + default: + throw new IllegalArgumentException("" + annotation.snippets()); } } @Override - public String[] junit() { - return annotation.junit(); + public Class objectFactory() { + return (annotation.objectFactory() == NoObjectFactory.class) ? null : annotation.objectFactory(); } } } diff --git a/junit/src/main/java/io/cucumber/junit/JUnitOptionsParser.java b/junit/src/main/java/io/cucumber/junit/JUnitOptionsParser.java index 3cad2aefef..42d5070978 100644 --- a/junit/src/main/java/io/cucumber/junit/JUnitOptionsParser.java +++ b/junit/src/main/java/io/cucumber/junit/JUnitOptionsParser.java @@ -1,46 +1,12 @@ package io.cucumber.junit; -import cucumber.runtime.CucumberException; -import cucumber.util.FixJava; - -import java.io.InputStreamReader; -import java.io.Reader; -import java.util.ArrayList; -import java.util.List; +import java.util.Map; final class JUnitOptionsParser { - private static final String OPTIONS_RESOURCE = "/io/cucumber/junit/api/OPTIONS.txt"; - private static String optionsText; - - /** - * Create a new instance from a list of options, for example: - *

- * - * - * @param args - * @return - */ - JUnitOptionsBuilder parse(List args) { - args = new ArrayList<>(args); - JUnitOptionsBuilder builder = new JUnitOptionsBuilder(); - - while (!args.isEmpty()) { - String arg = args.remove(0).trim(); - - if (arg.equals("--help") || arg.equals("-h")) { - printOptions(); - System.exit(0); - } else if (arg.equals("--no-filename-compatible-names") || arg.equals("--filename-compatible-names")) { - builder.setFilenameCompatibleNames(!arg.startsWith("--no-")); - } else if (arg.equals("--no-step-notifications") || arg.equals("--step-notifications")) { - builder.setStepNotifications(!arg.startsWith("--no-")); - } else { - printOptions(); - throw new CucumberException("Unknown option: " + arg); - } - } - return builder; + JUnitOptionsBuilder parse(Map properties) { + // TODO: Nothing to parse yet. See https://github.com/cucumber/cucumber-jvm/issues/1675 + return new JUnitOptionsBuilder(); } JUnitOptionsBuilder parse(Class clazz) { @@ -63,20 +29,5 @@ JUnitOptionsBuilder parse(Class clazz) { } return args; } - - private void printOptions() { - loadUsageTextIfNeeded(); - System.out.println(optionsText); - } - - private static void loadUsageTextIfNeeded() { - if (optionsText == null) { - try { - Reader reader = new InputStreamReader(FixJava.class.getResourceAsStream(OPTIONS_RESOURCE), "UTF-8"); - optionsText = FixJava.readReader(reader); - } catch (Exception e) { - optionsText = "Could not load usage text: " + e.toString(); - } - } - } } + diff --git a/junit/src/main/java/io/cucumber/junit/JUnitReporter.java b/junit/src/main/java/io/cucumber/junit/JUnitReporter.java index 7ce05db771..6750c815c5 100644 --- a/junit/src/main/java/io/cucumber/junit/JUnitReporter.java +++ b/junit/src/main/java/io/cucumber/junit/JUnitReporter.java @@ -1,15 +1,14 @@ package io.cucumber.junit; -import cucumber.api.PickleStepTestStep; -import cucumber.api.Result; -import cucumber.api.event.EventHandler; -import cucumber.api.event.TestCaseFinished; -import cucumber.api.event.TestCaseStarted; -import cucumber.api.event.TestStepFinished; -import cucumber.api.event.TestStepStarted; -import cucumber.runner.EventBus; +import io.cucumber.core.event.PickleStepTestStep; +import io.cucumber.core.event.EventHandler; +import io.cucumber.core.event.Result; +import io.cucumber.core.event.TestCaseFinished; +import io.cucumber.core.event.TestCaseStarted; +import io.cucumber.core.event.TestStepFinished; +import io.cucumber.core.event.TestStepStarted; import io.cucumber.junit.PickleRunners.PickleRunner; -import io.cucumber.junit.SkippedThrowable.NotificationLevel; +import io.cucumber.core.eventbus.EventBus; import org.junit.runner.Description; import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunNotifier; @@ -17,6 +16,9 @@ import java.util.ArrayList; +import static io.cucumber.junit.SkippedThrowable.NotificationLevel.SCENARIO; +import static io.cucumber.junit.SkippedThrowable.NotificationLevel.STEP; + final class JUnitReporter { private final JUnitOptions junitOptions; @@ -39,8 +41,8 @@ public void receive(TestCaseStarted event) { @Override public void receive(TestStepStarted event) { - if (event.testStep instanceof PickleStepTestStep) { - PickleStepTestStep testStep = (PickleStepTestStep) event.testStep; + if (event.getTestStep() instanceof PickleStepTestStep) { + PickleStepTestStep testStep = (PickleStepTestStep) event.getTestStep(); handleStepStarted(testStep.getPickleStep()); } } @@ -50,11 +52,11 @@ public void receive(TestStepStarted event) { @Override public void receive(TestStepFinished event) { - if (event.testStep instanceof PickleStepTestStep) { - PickleStepTestStep testStep = (PickleStepTestStep) event.testStep; - handleStepResult(testStep, event.result); + if (event.getTestStep() instanceof PickleStepTestStep) { + PickleStepTestStep testStep = (PickleStepTestStep) event.getTestStep(); + handleStepResult(testStep, event.getResult()); } else { - handleHookResult(event.result); + handleHookResult(event.getResult()); } } @@ -63,7 +65,7 @@ public void receive(TestStepFinished event) { @Override public void receive(TestCaseFinished event) { - handleTestCaseResult(event.result); + handleTestCaseResult(event.getResult()); } }; @@ -115,7 +117,7 @@ void handleStepResult(PickleStepTestStep testStep, Result result) { break; case SKIPPED: if (error == null) { - error = new SkippedThrowable(NotificationLevel.STEP); + error = new SkippedThrowable(STEP); } else { stepErrors.add(error); } @@ -156,17 +158,17 @@ void handleTestCaseResult(Result result) { break; case SKIPPED: if (stepErrors.isEmpty()) { - stepErrors.add(new SkippedThrowable(NotificationLevel.SCENARIO)); - } - for (Throwable error : stepErrors) { - pickleRunnerNotifier.addFailedAssumption(error); + stepErrors.add(new SkippedThrowable(SCENARIO)); } + stepErrors.stream() + .findFirst() + .ifPresent(error -> pickleRunnerNotifier.addFailedAssumption(error)); break; case PENDING: case UNDEFINED: - for (Throwable error : stepErrors) { - addFailureOrFailedAssumptionDependingOnStrictMode(pickleRunnerNotifier, error); - } + stepErrors.stream() + .findFirst() + .ifPresent(error -> addFailureOrFailedAssumptionDependingOnStrictMode(pickleRunnerNotifier, error)); break; case AMBIGUOUS: case FAILED: diff --git a/junit/src/main/java/io/cucumber/junit/NoObjectFactory.java b/junit/src/main/java/io/cucumber/junit/NoObjectFactory.java new file mode 100644 index 0000000000..c286963434 --- /dev/null +++ b/junit/src/main/java/io/cucumber/junit/NoObjectFactory.java @@ -0,0 +1,30 @@ +package io.cucumber.junit; + +import io.cucumber.core.backend.ObjectFactory; + +/** + * This object factory does nothing. It is solely needed for marking purposes. + */ +final class NoObjectFactory implements ObjectFactory { + + private NoObjectFactory() { + // No need for instantiation + } + + @Override + public boolean addClass(Class glueClass) { + return false; + } + + @Override + public T getInstance(Class glueClass) { + return null; + } + + @Override + public void start() {} + + @Override + public void stop() {} + +} diff --git a/junit/src/main/java/io/cucumber/junit/PickleRunners.java b/junit/src/main/java/io/cucumber/junit/PickleRunners.java index e089729091..0df6e1b68f 100644 --- a/junit/src/main/java/io/cucumber/junit/PickleRunners.java +++ b/junit/src/main/java/io/cucumber/junit/PickleRunners.java @@ -1,7 +1,7 @@ package io.cucumber.junit; -import cucumber.runner.Runner; -import cucumber.runner.RunnerSupplier; +import io.cucumber.core.runner.Runner; +import io.cucumber.core.runtime.RunnerSupplier; import gherkin.events.PickleEvent; import gherkin.pickles.PickleLocation; import gherkin.pickles.PickleStep; diff --git a/junit/src/main/resources/io/cucumber/junit/OPTIONS.txt b/junit/src/main/resources/io/cucumber/junit/OPTIONS.txt deleted file mode 100644 index 4b34b9b044..0000000000 --- a/junit/src/main/resources/io/cucumber/junit/OPTIONS.txt +++ /dev/null @@ -1,16 +0,0 @@ -JUnit Options: - - -h, --help You're looking at it. - --[no-]filename-compatible-names Make sure that the names of the test cases - only is made up of [A-Za-Z0-9_] so that the - names for certain can be used as file names. - For instance gradle will use these names in - the file names of the JUnit xml report files. - --[no-]step-notifications By default steps are not included in - notifications and descriptions. This aligns - test case in the Cucumber-JVM domain - (Scenarios) with the test case in the JUnit - domain (the leafs in the description tree), - and works better with the report files of - the notification listeners like maven surefire - or gradle. diff --git a/junit/src/test/java/cucumber/runtime/stub/StubBackend.java b/junit/src/test/java/cucumber/runtime/stub/StubBackend.java deleted file mode 100644 index e6a4d90998..0000000000 --- a/junit/src/test/java/cucumber/runtime/stub/StubBackend.java +++ /dev/null @@ -1,91 +0,0 @@ -package cucumber.runtime.stub; - -import cucumber.runtime.StepDefinition; -import io.cucumber.stepexpression.Argument; -import io.cucumber.stepexpression.TypeRegistry; -import cucumber.runtime.Backend; -import cucumber.runtime.Glue; -import cucumber.runtime.io.ResourceLoader; -import cucumber.runtime.snippets.FunctionNameGenerator; -import gherkin.pickles.PickleStep; - -import java.net.URI; -import java.util.Collections; -import java.util.List; - -import static java.util.Collections.singletonList; - -/** - * We need an implementation of Backend to prevent Runtime from blowing up. - */ -@SuppressWarnings("unused") -public class StubBackend implements Backend { - public StubBackend(ResourceLoader resourceLoader, TypeRegistry typeRegistry) { - - } - - @Override - public void loadGlue(Glue glue, List gluePaths) { - glue.addStepDefinition(createStepDefinition("background step")); - glue.addStepDefinition(createStepDefinition("scenario name")); - glue.addStepDefinition(createStepDefinition("scenario C")); - glue.addStepDefinition(createStepDefinition("scenario D")); - glue.addStepDefinition(createStepDefinition("scenario E")); - glue.addStepDefinition(createStepDefinition("first step")); - glue.addStepDefinition(createStepDefinition("second step")); - glue.addStepDefinition(createStepDefinition("third step")); - - } - - public StepDefinition createStepDefinition(final String pattern) { - return new StepDefinition() { - @Override - public List matchedArguments(PickleStep step) { - return pattern.equals(step.getText()) ? Collections.emptyList() : null; - } - - @Override - public String getLocation(boolean detail) { - return null; - } - - @Override - public Integer getParameterCount() { - return 0; - } - - @Override - public void execute(Object[] args) { - - } - - @Override - public boolean isDefinedAt(StackTraceElement stackTraceElement) { - return false; - } - - @Override - public String getPattern() { - return pattern; - } - - @Override - public boolean isScenarioScoped() { - return false; - } - }; - } - - @Override - public void buildWorld() { - } - - @Override - public void disposeWorld() { - } - - @Override - public List getSnippet(PickleStep step, String keyword, FunctionNameGenerator functionNameGenerator) { - return singletonList("STUB SNIPPET"); - } -} diff --git a/junit/src/test/java/io/cucumber/junit/AssertionsTest.java b/junit/src/test/java/io/cucumber/junit/AssertionsTest.java index 19e3ff3af0..5d1f1b2c6c 100644 --- a/junit/src/test/java/io/cucumber/junit/AssertionsTest.java +++ b/junit/src/test/java/io/cucumber/junit/AssertionsTest.java @@ -1,17 +1,25 @@ package io.cucumber.junit; -import cucumber.runtime.CucumberException; +import io.cucumber.core.exception.CucumberException; import org.junit.Test; +import org.junit.jupiter.api.function.Executable; import org.junit.runner.RunWith; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.jupiter.api.Assertions.assertThrows; + public class AssertionsTest { - @Test(expected = CucumberException.class) + @Test public void should_throw_cucumber_exception_when_annotated() { - Assertions.assertNoCucumberAnnotatedMethods(WithCucumberMethod.class); + final Executable testMethod = () -> Assertions.assertNoCucumberAnnotatedMethods(WithCucumberMethod.class); + final CucumberException expectedThrown = assertThrows(CucumberException.class, testMethod); + assertThat(expectedThrown.getMessage(), is(equalTo("\n\nClasses annotated with @RunWith(Cucumber.class) must not define any\nStep Definition or Hook methods. Their sole purpose is to serve as\nan entry point for JUnit. Step Definitions and Hooks should be defined\nin their own classes. This allows them to be reused across features.\nOffending class: class io.cucumber.junit.AssertionsTest$WithCucumberMethod\n"))); } @RunWith(Cucumber.class) diff --git a/junit/src/test/java/io/cucumber/junit/CucumberTest.java b/junit/src/test/java/io/cucumber/junit/CucumberTest.java index 88c3b0d201..abe6d50732 100644 --- a/junit/src/test/java/io/cucumber/junit/CucumberTest.java +++ b/junit/src/test/java/io/cucumber/junit/CucumberTest.java @@ -1,12 +1,13 @@ package io.cucumber.junit; -import cucumber.runtime.CucumberException; import gherkin.ParserException.CompositeParserException; +import io.cucumber.core.exception.CucumberException; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.ParallelComputer; +import org.junit.jupiter.api.function.Executable; import org.junit.rules.ExpectedException; import org.junit.runner.Description; import org.junit.runner.Request; @@ -27,10 +28,12 @@ import static java.util.Collections.emptyList; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.isA; +import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.argThat; public class CucumberTest { @@ -179,9 +182,11 @@ public void no_stepdefs_in_cucumber_runner_valid() { Assertions.assertNoCucumberAnnotatedMethods(ValidIgnored.class); } - @Test(expected = CucumberException.class) + @Test public void no_stepdefs_in_cucumber_runner_invalid() { - Assertions.assertNoCucumberAnnotatedMethods(Invalid.class); + final Executable testMethod = () -> Assertions.assertNoCucumberAnnotatedMethods(Invalid.class); + final CucumberException expectedThrown = assertThrows(CucumberException.class, testMethod); + assertThat(expectedThrown.getMessage(), is(equalTo("\n\nClasses annotated with @RunWith(Cucumber.class) must not define any\nStep Definition or Hook methods. Their sole purpose is to serve as\nan entry point for JUnit. Step Definitions and Hooks should be defined\nin their own classes. This allows them to be reused across features.\nOffending class: class io.cucumber.junit.CucumberTest$Invalid\n"))); } public class ImplicitFeatureAndGluePath { diff --git a/junit/src/test/java/io/cucumber/junit/FeatureRunnerTest.java b/junit/src/test/java/io/cucumber/junit/FeatureRunnerTest.java index 2ca2295a19..44de8db0ec 100644 --- a/junit/src/test/java/io/cucumber/junit/FeatureRunnerTest.java +++ b/junit/src/test/java/io/cucumber/junit/FeatureRunnerTest.java @@ -1,26 +1,36 @@ package io.cucumber.junit; -import cucumber.runner.TimeServiceEventBus; -import cucumber.runner.EventBus; -import cucumber.runner.TimeService; -import cucumber.runtime.Backend; -import cucumber.runtime.BackendSupplier; +import io.cucumber.core.backend.ObjectFactoryServiceLoader; +import io.cucumber.core.eventbus.EventBus; +import io.cucumber.core.feature.CucumberFeature; +import io.cucumber.core.filter.Filters; +import io.cucumber.core.io.ClassFinder; +import io.cucumber.core.io.MultiLoader; +import io.cucumber.core.io.ResourceLoader; +import io.cucumber.core.io.ResourceLoaderClassFinder; import io.cucumber.core.options.RuntimeOptions; -import cucumber.runner.ThreadLocalRunnerSupplier; -import cucumber.runtime.filter.Filters; -import cucumber.runtime.model.CucumberFeature; +import io.cucumber.core.runtime.BackendSupplier; +import io.cucumber.core.runtime.ScanningTypeRegistryConfigurerSupplier; +import io.cucumber.core.runtime.ObjectFactorySupplier; +import io.cucumber.core.runtime.RunnerSupplier; +import io.cucumber.core.runtime.SingletonObjectFactorySupplier; +import io.cucumber.core.runtime.ThreadLocalRunnerSupplier; +import io.cucumber.core.runtime.TimeServiceEventBus; import org.junit.Test; import org.junit.runner.Description; +import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunNotifier; import org.junit.runners.model.InitializationError; +import org.mockito.ArgumentCaptor; import org.mockito.InOrder; -import java.util.Arrays; -import java.util.Collection; +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; import java.util.HashSet; import java.util.Set; -import static java.util.Arrays.asList; +import static java.util.Collections.singleton; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.argThat; @@ -65,7 +75,7 @@ public void should_not_create_step_descriptions_by_default() throws Exception { ); - FeatureRunner runner = createFeatureRunner(cucumberFeature); + FeatureRunner runner = createFeatureRunner(cucumberFeature, new JUnitOptions()); Description feature = runner.getDescription(); Description scenarioA = feature.getChildren().get(0); @@ -97,18 +107,18 @@ public void should_not_issue_notification_for_steps_by_default_scenario_outline_ " | x | y |\n" + " | second | third |\n"); - RunNotifier notifier = runFeatureWithNotifier(feature); + RunNotifier notifier = runFeatureWithNotifier(feature, new JUnitOptions()); InOrder order = inOrder(notifier); order.verify(notifier).fireTestStarted(argThat(new DescriptionMatcher("scenario outline name(feature name)"))); - order.verify(notifier, times(3)).fireTestAssumptionFailed(argThat(new FailureMatcher("scenario outline name(feature name)"))); + order.verify(notifier, times(1)).fireTestAssumptionFailed(argThat(new FailureMatcher("scenario outline name(feature name)"))); order.verify(notifier).fireTestFinished(argThat(new DescriptionMatcher("scenario outline name(feature name)"))); order.verify(notifier).fireTestStarted(argThat(new DescriptionMatcher("scenario outline name(feature name)"))); - order.verify(notifier, times(3)).fireTestAssumptionFailed(argThat(new FailureMatcher("scenario outline name(feature name)"))); + order.verify(notifier, times(1)).fireTestAssumptionFailed(argThat(new FailureMatcher("scenario outline name(feature name)"))); order.verify(notifier).fireTestFinished(argThat(new DescriptionMatcher("scenario outline name(feature name)"))); order.verify(notifier).fireTestStarted(argThat(new DescriptionMatcher("scenario outline name(feature name)"))); - order.verify(notifier, times(3)).fireTestAssumptionFailed(argThat(new FailureMatcher("scenario outline name(feature name)"))); + order.verify(notifier, times(1)).fireTestAssumptionFailed(argThat(new FailureMatcher("scenario outline name(feature name)"))); order.verify(notifier).fireTestFinished(argThat(new DescriptionMatcher("scenario outline name(feature name)"))); } @@ -124,54 +134,55 @@ public void should_not_issue_notification_for_steps_by_default_two_scenarios_wit " Scenario: scenario_2 name\n" + " Then another second step\n"); - RunNotifier notifier = runFeatureWithNotifier(feature); + RunNotifier notifier = runFeatureWithNotifier(feature, new JUnitOptions()); InOrder order = inOrder(notifier); order.verify(notifier).fireTestStarted(argThat(new DescriptionMatcher("scenario_1 name(feature name)"))); - order.verify(notifier, times(3)).fireTestAssumptionFailed(argThat(new FailureMatcher("scenario_1 name(feature name)"))); + order.verify(notifier, times(1)).fireTestAssumptionFailed(argThat(new FailureMatcher("scenario_1 name(feature name)"))); order.verify(notifier).fireTestFinished(argThat(new DescriptionMatcher("scenario_1 name(feature name)"))); order.verify(notifier).fireTestStarted(argThat(new DescriptionMatcher("scenario_2 name(feature name)"))); - order.verify(notifier, times(2)).fireTestAssumptionFailed(argThat(new FailureMatcher("scenario_2 name(feature name)"))); + order.verify(notifier, times(1)).fireTestAssumptionFailed(argThat(new FailureMatcher("scenario_2 name(feature name)"))); order.verify(notifier).fireTestFinished(argThat(new DescriptionMatcher("scenario_2 name(feature name)"))); } - private RunNotifier runFeatureWithNotifier(CucumberFeature cucumberFeature, String... options) throws InitializationError { + private RunNotifier runFeatureWithNotifier(CucumberFeature cucumberFeature, JUnitOptions options) throws InitializationError { FeatureRunner runner = createFeatureRunner(cucumberFeature, options); RunNotifier notifier = mock(RunNotifier.class); runner.run(notifier); return notifier; } - private FeatureRunner createFeatureRunner(CucumberFeature cucumberFeature, String... options) throws InitializationError { - JUnitOptions junitOption = new JUnitOptionsParser().parse(Arrays.asList(options)).build(); - return createFeatureRunner(cucumberFeature, junitOption); - } - private FeatureRunner createFeatureRunner(CucumberFeature cucumberFeature, JUnitOptions junitOption) throws InitializationError { + ObjectFactoryServiceLoader objectFactoryServiceLoader = new ObjectFactoryServiceLoader(RuntimeOptions.defaultOptions()); + ObjectFactorySupplier objectFactory = new SingletonObjectFactorySupplier(objectFactoryServiceLoader); final RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); - final TimeService timeServiceStub = new TimeService() { + final Clock clockStub = new Clock() { @Override - public long time() { - return 0L; + public Instant instant() { + return Instant.EPOCH; } @Override - public long timeMillis() { - return 0L; + public ZoneId getZone() { + return null; } - }; - BackendSupplier backendSupplier = new BackendSupplier() { + @Override - public Collection get() { - return asList(mock(Backend.class)); + public Clock withZone(ZoneId zone) { + return null; } }; + BackendSupplier backendSupplier = () -> singleton(new StubBackendProviderService.StubBackend()); - EventBus bus = new TimeServiceEventBus(timeServiceStub); + EventBus bus = new TimeServiceEventBus(clockStub); Filters filters = new Filters(runtimeOptions); - ThreadLocalRunnerSupplier runnerSupplier = new ThreadLocalRunnerSupplier(runtimeOptions, bus, backendSupplier); + ClassLoader classLoader = ClassLoader.getSystemClassLoader(); + ResourceLoader resourceLoader = new MultiLoader(classLoader); + ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader); + ScanningTypeRegistryConfigurerSupplier typeRegistrySupplier = new ScanningTypeRegistryConfigurerSupplier(classFinder, runtimeOptions); + ThreadLocalRunnerSupplier runnerSupplier = new ThreadLocalRunnerSupplier(runtimeOptions, bus, backendSupplier, objectFactory, typeRegistrySupplier); return new FeatureRunner(cucumberFeature, filters, runnerSupplier, junitOption); } @@ -195,10 +206,10 @@ public void should_populate_descriptions_with_stable_unique_ids() throws Excepti ); - FeatureRunner runner = createFeatureRunner(cucumberFeature); - FeatureRunner rerunner = createFeatureRunner(cucumberFeature); + FeatureRunner runner = createFeatureRunner(cucumberFeature, new JUnitOptions()); + FeatureRunner rerunner = createFeatureRunner(cucumberFeature, new JUnitOptions()); - Set descriptions = new HashSet(); + Set descriptions = new HashSet<>(); assertDescriptionIsUnique(runner.getDescription(), descriptions); assertDescriptionIsPredictable(runner.getDescription(), descriptions); assertDescriptionIsPredictable(rerunner.getDescription(), descriptions); @@ -225,7 +236,8 @@ public void step_descriptions_can_be_turned_on() throws Exception { ); - FeatureRunner runner = createFeatureRunner(cucumberFeature, "--step-notifications"); + JUnitOptions junitOption = new JUnitOptionsBuilder().setStepNotifications(true).build(); + FeatureRunner runner = createFeatureRunner(cucumberFeature, junitOption); Description feature = runner.getDescription(); Description scenarioA = feature.getChildren().get(0); @@ -257,7 +269,8 @@ public void step_notification_can_be_turned_on_scenario_outline_with_two_example " | x | y |\n" + " | second | third |\n"); - RunNotifier notifier = runFeatureWithNotifier(feature, "--step-notifications"); + JUnitOptions junitOption = new JUnitOptionsBuilder().setStepNotifications(true).build(); + RunNotifier notifier = runFeatureWithNotifier(feature, junitOption); InOrder order = inOrder(notifier); @@ -308,7 +321,8 @@ public void step_notification_can_be_turned_on_two_scenarios_with_background() t " Scenario: scenario_2 name\n" + " Then another second step\n"); - RunNotifier notifier = runFeatureWithNotifier(feature, "--step-notifications"); + JUnitOptions junitOption = new JUnitOptionsBuilder().setStepNotifications(true).build(); + RunNotifier notifier = runFeatureWithNotifier(feature, junitOption); InOrder order = inOrder(notifier); @@ -333,5 +347,37 @@ public void step_notification_can_be_turned_on_two_scenarios_with_background() t order.verify(notifier).fireTestFinished(argThat(new DescriptionMatcher("scenario_2 name"))); } + @Test + public void should_notify_of_failure_to_create_runners_and_request_test_execution_to_stop() throws InitializationError { + CucumberFeature feature = TestPickleBuilder.parseFeature("path/test.feature", "" + + "Feature: feature name\n" + + " Scenario: scenario_1 name\n" + + " Given first step\n" + ); + + Filters filters = new Filters(RuntimeOptions.defaultOptions()); + + IllegalStateException illegalStateException = new IllegalStateException(); + RunnerSupplier runnerSupplier = () -> { + throw illegalStateException; + }; + + FeatureRunner featureRunner = new FeatureRunner(feature, filters, runnerSupplier, new JUnitOptions()); + + RunNotifier notifier = mock(RunNotifier.class); + featureRunner.runChild(featureRunner.getChildren().get(0), notifier); + + Description description = featureRunner.getDescription(); + ArgumentCaptor failureArgumentCaptor = ArgumentCaptor.forClass(Failure.class); + + InOrder order = inOrder(notifier); + order.verify(notifier).fireTestStarted(description); + order.verify(notifier).fireTestFailure(failureArgumentCaptor.capture()); + assertEquals(illegalStateException, failureArgumentCaptor.getValue().getException()); + assertEquals(description, failureArgumentCaptor.getValue().getDescription()); + order.verify(notifier).pleaseStop(); + order.verify(notifier).fireTestFinished(description); + } + } diff --git a/junit/src/test/java/io/cucumber/junit/InvokeMethodsAroundEventsTest.java b/junit/src/test/java/io/cucumber/junit/InvokeMethodsAroundEventsTest.java index 6c943aa111..5d7b6beb74 100644 --- a/junit/src/test/java/io/cucumber/junit/InvokeMethodsAroundEventsTest.java +++ b/junit/src/test/java/io/cucumber/junit/InvokeMethodsAroundEventsTest.java @@ -1,10 +1,9 @@ package io.cucumber.junit; -import cucumber.api.event.ConcurrentEventListener; -import cucumber.api.event.EventHandler; -import cucumber.api.event.EventPublisher; -import cucumber.api.event.TestRunFinished; -import cucumber.api.event.TestRunStarted; +import io.cucumber.core.plugin.ConcurrentEventListener; +import io.cucumber.core.event.EventPublisher; +import io.cucumber.core.event.TestRunFinished; +import io.cucumber.core.event.TestRunStarted; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -21,19 +20,6 @@ public class InvokeMethodsAroundEventsTest { private static final List events = new ArrayList<>(); - private static EventHandler testRunStartedEventHandler = new EventHandler() { - @Override - public void receive(TestRunStarted event) { - events.add("TestRunStarted"); - } - }; - private static EventHandler testRunFinishedEventHandler = new EventHandler() { - @Override - public void receive(TestRunFinished event) { - events.add("TestRunFinished"); - } - }; - @AfterClass public static void afterClass() { events.clear(); @@ -52,7 +38,6 @@ public static class BeforeAfterClass { @BeforeClass public static void beforeClass() { events.add("BeforeClass"); - } @AfterClass @@ -66,8 +51,8 @@ public static class TestRunStartedFinishedListener implements ConcurrentEventLis @Override public void setEventPublisher(EventPublisher publisher) { - publisher.registerHandlerFor(TestRunStarted.class, testRunStartedEventHandler); - publisher.registerHandlerFor(TestRunFinished.class, testRunFinishedEventHandler); + publisher.registerHandlerFor(TestRunStarted.class, event -> events.add("TestRunStarted")); + publisher.registerHandlerFor(TestRunFinished.class, event -> events.add("TestRunFinished")); } } diff --git a/junit/src/test/java/io/cucumber/junit/JUnitCucumberOptionsProviderTest.java b/junit/src/test/java/io/cucumber/junit/JUnitCucumberOptionsProviderTest.java new file mode 100644 index 0000000000..1f2748c5a2 --- /dev/null +++ b/junit/src/test/java/io/cucumber/junit/JUnitCucumberOptionsProviderTest.java @@ -0,0 +1,57 @@ +package io.cucumber.junit; + + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.cucumber.core.backend.ObjectFactory; + + +final class JUnitCucumberOptionsProviderTest { + + private JUnitCucumberOptionsProvider optionsProvider; + + @BeforeEach + void setUp() throws Exception { + this.optionsProvider = new JUnitCucumberOptionsProvider(); + } + + @Test + void testObjectFactoryWhenNotSpecified() { + io.cucumber.core.options.CucumberOptionsAnnotationParser.CucumberOptions options = this.optionsProvider.getOptions(ClassWithDefault.class); + assertNull(options.objectFactory()); + } + + @Test + void testObjectFactory() { + io.cucumber.core.options.CucumberOptionsAnnotationParser.CucumberOptions options = this.optionsProvider.getOptions(ClassWithCustomObjectFactory.class); + assertNotNull(options.objectFactory()); + assertEquals(TestObjectFactory.class, options.objectFactory()); + } + + @CucumberOptions() + private static final class ClassWithDefault {} + + @CucumberOptions(objectFactory = TestObjectFactory.class) + private static final class ClassWithCustomObjectFactory {} + + private static final class TestObjectFactory implements ObjectFactory { + @Override + public boolean addClass(Class glueClass) { + return false; + } + + @Override + public T getInstance(Class glueClass) { + return null; + } + + @Override + public void start() {} + + @Override + public void stop() {} + } +} diff --git a/junit/src/test/java/io/cucumber/junit/JUnitReporterTest.java b/junit/src/test/java/io/cucumber/junit/JUnitReporterTest.java index 19b728df0a..754b459998 100644 --- a/junit/src/test/java/io/cucumber/junit/JUnitReporterTest.java +++ b/junit/src/test/java/io/cucumber/junit/JUnitReporterTest.java @@ -1,8 +1,9 @@ package io.cucumber.junit; -import cucumber.api.PickleStepTestStep; -import cucumber.api.Result; -import cucumber.runner.EventBus; +import io.cucumber.core.event.PickleStepTestStep; +import io.cucumber.core.event.Result; +import io.cucumber.core.event.Status; +import io.cucumber.core.eventbus.EventBus; import io.cucumber.junit.JUnitReporter.EachTestNotifier; import io.cucumber.junit.JUnitReporter.NoTestNotifier; import io.cucumber.junit.PickleRunners.PickleRunner; @@ -26,6 +27,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static java.time.Duration.ZERO; import static java.util.Arrays.asList; @@ -37,7 +39,7 @@ public class JUnitReporterTest { @Test public void test_case_started_fires_test_started_for_pickle() { createNonStrictReporter(); - PickleRunner pickleRunner = mockPickleRunner(Collections.emptyList()); + PickleRunner pickleRunner = mockPickleRunner(Collections.emptyList()); runNotifier = mock(RunNotifier.class); jUnitReporter.startExecutionUnit(pickleRunner, runNotifier); @@ -61,7 +63,7 @@ public void test_step_started_does_not_fire_test_started_for_step_by_default() { @Test public void test_step_started_fires_test_started_for_step_when_using_step_notifications() { - createNonStrictReporter("--step-notifications"); + createReporter(new JUnitOptionsBuilder().setStepNotifications(true).build()); PickleStep runnerStep = mockStep(); PickleRunner pickleRunner = mockPickleRunner(runnerSteps(runnerStep)); runNotifier = mock(RunNotifier.class); @@ -78,7 +80,7 @@ public void test_step_finished_fires_only_test_finished_for_passed_step() { createDefaultRunNotifier(); Description description = mock(Description.class); setUpStepNotifierAndStepErrors(description); - Result result = mockResult(Result.Type.PASSED); + Result result = mockResult(Status.PASSED); jUnitReporter.handleStepResult(mock(PickleStepTestStep.class), result); @@ -91,7 +93,7 @@ public void test_step_finished_fires_assumption_failed_and_test_finished_for_ski createDefaultRunNotifier(); Description description = mock(Description.class); setUpStepNotifierAndStepErrors(description); - Result result = mockResult(Result.Type.SKIPPED); + Result result = mockResult(Status.SKIPPED); jUnitReporter.handleStepResult(mock(PickleStepTestStep.class), result); @@ -112,7 +114,7 @@ public void test_step_finished_fires_assumption_failed_and_test_finished_for_ski Description description = mock(Description.class); setUpStepNotifierAndStepErrors(description); Throwable exception = new AssumptionViolatedException("Oops"); - Result result = mockResult(Result.Type.SKIPPED, exception); + Result result = mockResult(Status.SKIPPED, exception); jUnitReporter.handleStepResult(mock(PickleStepTestStep.class), result); @@ -130,7 +132,7 @@ public void test_step_finished_adds_no_step_exeption_for_skipped_step_without_ex createNonStrictReporter(); createDefaultRunNotifier(); setUpNoStepNotifierAndStepErrors(); - Result result = mockResult(Result.Type.SKIPPED); + Result result = mockResult(Status.SKIPPED); jUnitReporter.handleStepResult(mock(PickleStepTestStep.class), result); @@ -143,7 +145,7 @@ public void test_step_finished_adds_the_step_exeption_for_skipped_step_with_assu createDefaultRunNotifier(); setUpNoStepNotifierAndStepErrors(); Throwable exception = new AssumptionViolatedException("Oops"); - Result result = mockResult(Result.Type.SKIPPED, exception); + Result result = mockResult(Status.SKIPPED, exception); jUnitReporter.handleStepResult(mock(PickleStepTestStep.class), result); @@ -157,7 +159,7 @@ public void test_step_finished_fires_assumption_failed_and_test_finished_for_pen Description description = mock(Description.class); setUpStepNotifierAndStepErrors(description); Throwable exception = new TestPendingException(); - Result result = mockResult(Result.Type.PENDING, exception); + Result result = mockResult(Status.PENDING, exception); jUnitReporter.handleStepResult(mock(PickleStepTestStep.class), result); @@ -177,7 +179,7 @@ public void test_step_finished_fires_assumption_failed_and_test_finished_for_pen Description description = mock(Description.class); setUpStepNotifierAndStepErrors(description); Throwable exception = new TestPendingException(); - Result result = mockResult(Result.Type.PENDING, exception); + Result result = mockResult(Status.PENDING, exception); jUnitReporter.handleStepResult(mock(PickleStepTestStep.class), result); @@ -196,7 +198,7 @@ public void test_step_finished_adds_the_step_exeption_for_pending_steps() { createDefaultRunNotifier(); setUpNoStepNotifierAndStepErrors(); Throwable exception = new TestPendingException(); - Result result = mockResult(Result.Type.PENDING, exception); + Result result = mockResult(Status.PENDING, exception); jUnitReporter.handleStepResult(mock(PickleStepTestStep.class), result); @@ -209,7 +211,7 @@ public void test_step_finished_fires_assumption_failed_and_test_finished_for_und createDefaultRunNotifier(); Description description = mock(Description.class); setUpStepNotifierAndStepErrors(description); - Result result = mockResult(Result.Type.UNDEFINED); + Result result = mockResult(Status.UNDEFINED); jUnitReporter.handleStepResult(mock(PickleStepTestStep.class), result); @@ -229,7 +231,7 @@ public void test_step_finished_fires_failure_and_test_finished_for_undefined_ste createDefaultRunNotifier(); Description description = mock(Description.class); setUpStepNotifierAndStepErrors(description); - Result result = mockResult(Result.Type.UNDEFINED); + Result result = mockResult(Status.UNDEFINED); jUnitReporter.handleStepResult(mock(PickleStepTestStep.class), result); @@ -249,7 +251,7 @@ public void test_step_finished_adds_a_step_exeption_for_undefined_steps() { createDefaultRunNotifier(); setUpNoStepNotifierAndStepErrors(); PickleStepTestStep testStep = mockTestStep("XX"); - Result result = mockResult(Result.Type.UNDEFINED); + Result result = mockResult(Status.UNDEFINED); jUnitReporter.handleStepResult(testStep, result); @@ -264,7 +266,7 @@ public void test_step_finished_fires_failure_and_test_finished_for_failed_step() Description description = mock(Description.class); setUpStepNotifierAndStepErrors(description); Throwable exception = mock(Throwable.class); - Result result = mockResult(Result.Type.FAILED, exception); + Result result = mockResult(Status.FAILED, exception); jUnitReporter.handleStepResult(mock(PickleStepTestStep.class), result); @@ -283,7 +285,7 @@ public void test_step_finished_adds_the_step_exeption_for_failed_steps() { createDefaultRunNotifier(); setUpNoStepNotifierAndStepErrors(); Throwable exception = new TestPendingException(); - Result result = mockResult(Result.Type.FAILED, exception); + Result result = mockResult(Status.FAILED, exception); jUnitReporter.handleStepResult(mock(PickleStepTestStep.class), result); @@ -295,7 +297,7 @@ public void test_case_finished_fires_only_test_finished_for_passed_step() { createNonStrictReporter(); Description description = mock(Description.class); createRunNotifier(description); - Result result = mockResult(Result.Type.PASSED); + Result result = mockResult(Status.PASSED); jUnitReporter.handleTestCaseResult(result); @@ -308,7 +310,7 @@ public void test_case_finished_fires_assumption_failed_and_test_finished_for_ski Description description = mock(Description.class); createRunNotifier(description); populateStepErrors(Collections.emptyList()); - Result result = mockResult(Result.Type.SKIPPED); + Result result = mockResult(Status.SKIPPED); jUnitReporter.handleTestCaseResult(result); @@ -329,19 +331,17 @@ public void test_case_finished_fires_assumption_failed_and_test_finished_for_ski Throwable exception1 = mock(AssumptionViolatedException.class); Throwable exception2 = mock(AssumptionViolatedException.class); populateStepErrors(asList(exception1, exception2)); - Result result = mockResult(Result.Type.SKIPPED); + Result result = mockResult(Status.SKIPPED); jUnitReporter.handleTestCaseResult(result); ArgumentCaptor failureArgumentCaptor = ArgumentCaptor.forClass(Failure.class); - verify(runNotifier, times(2)).fireTestAssumptionFailed(failureArgumentCaptor.capture()); + verify(runNotifier, times(1)).fireTestAssumptionFailed(failureArgumentCaptor.capture()); verify(runNotifier).fireTestFinished(description); List failures = failureArgumentCaptor.getAllValues(); assertEquals(description, failures.get(0).getDescription()); assertEquals(exception1, failures.get(0).getException()); - assertEquals(description, failures.get(1).getDescription()); - assertEquals(exception2, failures.get(1).getException()); } @Test @@ -352,19 +352,17 @@ public void test_case_finished_fires_assumption_failed_and_test_finished_for_pen Throwable exception1 = mock(Throwable.class); Throwable exception2 = mock(Throwable.class); populateStepErrors(asList(exception1, exception2)); - Result result = mockResult(Result.Type.PENDING); + Result result = mockResult(Status.PENDING); jUnitReporter.handleTestCaseResult(result); ArgumentCaptor failureArgumentCaptor = ArgumentCaptor.forClass(Failure.class); - verify(runNotifier, times(2)).fireTestAssumptionFailed(failureArgumentCaptor.capture()); + verify(runNotifier, times(1)).fireTestAssumptionFailed(failureArgumentCaptor.capture()); verify(runNotifier).fireTestFinished(description); List failures = failureArgumentCaptor.getAllValues(); assertEquals(description, failures.get(0).getDescription()); assertEquals(exception1, failures.get(0).getException()); - assertEquals(description, failures.get(1).getDescription()); - assertEquals(exception2, failures.get(1).getException()); } @Test @@ -375,19 +373,17 @@ public void test_case_finished_fires_failure_and_test_finished_for_pending_step_ Throwable exception1 = mock(Throwable.class); Throwable exception2 = mock(Throwable.class); populateStepErrors(asList(exception1, exception2)); - Result result = mockResult(Result.Type.PENDING); + Result result = mockResult(Status.PENDING); jUnitReporter.handleTestCaseResult(result); ArgumentCaptor failureArgumentCaptor = ArgumentCaptor.forClass(Failure.class); - verify(runNotifier, times(2)).fireTestFailure(failureArgumentCaptor.capture()); + verify(runNotifier, times(1)).fireTestFailure(failureArgumentCaptor.capture()); verify(runNotifier).fireTestFinished(description); List failures = failureArgumentCaptor.getAllValues(); assertEquals(description, failures.get(0).getDescription()); assertEquals(exception1, failures.get(0).getException()); - assertEquals(description, failures.get(1).getDescription()); - assertEquals(exception2, failures.get(1).getException()); } @Test @@ -398,19 +394,17 @@ public void test_case_finished_fires_assumption_failed_and_test_finished_for_und Throwable exception1 = mock(Throwable.class); Throwable exception2 = mock(Throwable.class); populateStepErrors(asList(exception1, exception2)); - Result result = mockResult(Result.Type.UNDEFINED); + Result result = mockResult(Status.UNDEFINED); jUnitReporter.handleTestCaseResult(result); ArgumentCaptor failureArgumentCaptor = ArgumentCaptor.forClass(Failure.class); - verify(runNotifier, times(2)).fireTestAssumptionFailed(failureArgumentCaptor.capture()); + verify(runNotifier, times(1)).fireTestAssumptionFailed(failureArgumentCaptor.capture()); verify(runNotifier).fireTestFinished(description); List failures = failureArgumentCaptor.getAllValues(); assertEquals(description, failures.get(0).getDescription()); assertEquals(exception1, failures.get(0).getException()); - assertEquals(description, failures.get(1).getDescription()); - assertEquals(exception2, failures.get(1).getException()); } @Test @@ -421,19 +415,17 @@ public void test_case_finished_fires_failure_and_test_finished_for_undefined_ste Throwable exception1 = mock(Throwable.class); Throwable exception2 = mock(Throwable.class); populateStepErrors(asList(exception1, exception2)); - Result result = mockResult(Result.Type.UNDEFINED); + Result result = mockResult(Status.UNDEFINED); jUnitReporter.handleTestCaseResult(result); ArgumentCaptor failureArgumentCaptor = ArgumentCaptor.forClass(Failure.class); - verify(runNotifier, times(2)).fireTestFailure(failureArgumentCaptor.capture()); + verify(runNotifier, times(1)).fireTestFailure(failureArgumentCaptor.capture()); verify(runNotifier).fireTestFinished(description); List failures = failureArgumentCaptor.getAllValues(); assertEquals(description, failures.get(0).getDescription()); assertEquals(exception1, failures.get(0).getException()); - assertEquals(description, failures.get(1).getDescription()); - assertEquals(exception2, failures.get(1).getException()); } @Test @@ -444,7 +436,7 @@ public void test_case_finished_fires_failure_and_test_finished_for_failed_step() Throwable exception1 = mock(Throwable.class); Throwable exception2 = mock(Throwable.class); populateStepErrors(asList(exception1, exception2)); - Result result = mockResult(Result.Type.FAILED); + Result result = mockResult(Status.FAILED); jUnitReporter.handleTestCaseResult(result); @@ -459,12 +451,12 @@ public void test_case_finished_fires_failure_and_test_finished_for_failed_step() assertEquals(exception2, failures.get(1).getException()); } - private Result mockResult(Result.Type status, Throwable exception) { - return new Result(status, 0L, exception); + private Result mockResult(Status status, Throwable exception) { + return new Result(status, ZERO, exception); } - private Result mockResult(Result.Type status) { - return new Result(status, 0L, null); + private Result mockResult(Status status) { + return new Result(status, ZERO, null); } private PickleRunner mockPickleRunner(List runnerSteps) { @@ -515,26 +507,30 @@ private void createRunNotifier(Description description) { jUnitReporter.startExecutionUnit(pickleRunner, runNotifier); } - private void createStrictReporter(String... options) { - jUnitReporter = new JUnitReporter(mock(EventBus.class), new JUnitOptionsParser().parse(asList(options)).setStrict(true).build()); + private void createStrictReporter() { + createReporter(new JUnitOptionsBuilder().setStrict(true).build()); + } + + private void createNonStrictReporter() { + createReporter(new JUnitOptions()); } - private void createNonStrictReporter(String... options) { - jUnitReporter = new JUnitReporter(mock(EventBus.class), new JUnitOptionsParser().parse(asList(options)).build()); + private void createReporter(JUnitOptions options) { + jUnitReporter = new JUnitReporter(mock(EventBus.class), options); } private void setUpStepNotifierAndStepErrors(Description description) { jUnitReporter.stepNotifier = new EachTestNotifier(runNotifier, description); - jUnitReporter.stepErrors = new ArrayList(); + jUnitReporter.stepErrors = new ArrayList<>(); } private void setUpNoStepNotifierAndStepErrors() { jUnitReporter.stepNotifier = new NoTestNotifier(); - jUnitReporter.stepErrors = new ArrayList(); + jUnitReporter.stepErrors = new ArrayList<>(); } private void populateStepErrors(List exceptions) { - jUnitReporter.stepErrors = new ArrayList(); + jUnitReporter.stepErrors = new ArrayList<>(); for (Throwable exception : exceptions) { jUnitReporter.stepErrors.add(exception); } diff --git a/junit/src/test/java/io/cucumber/junit/PickleRunnerWithNoStepDescriptionsTest.java b/junit/src/test/java/io/cucumber/junit/PickleRunnerWithNoStepDescriptionsTest.java index 90c98db8c6..02e48354c2 100644 --- a/junit/src/test/java/io/cucumber/junit/PickleRunnerWithNoStepDescriptionsTest.java +++ b/junit/src/test/java/io/cucumber/junit/PickleRunnerWithNoStepDescriptionsTest.java @@ -1,12 +1,10 @@ package io.cucumber.junit; -import cucumber.runner.RunnerSupplier; import gherkin.events.PickleEvent; +import io.cucumber.core.runtime.RunnerSupplier; import io.cucumber.junit.PickleRunners.PickleRunner; import org.junit.Test; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import static org.junit.Assert.assertEquals; @@ -15,41 +13,41 @@ public class PickleRunnerWithNoStepDescriptionsTest { @Test - public void shouldUseScenarioNameWithFeatureNameAsClassNameForDisplayName() throws Exception { + public void shouldUseScenarioNameWithFeatureNameAsClassNameForDisplayName() { List pickles = TestPickleBuilder.pickleEventsFromFeature("featurePath", "" + - "Feature: feature name\n" + - " Scenario: scenario name\n" + - " Then it works\n"); + "Feature: feature name\n" + + " Scenario: scenario name\n" + + " Then it works\n"); PickleRunner runner = PickleRunners.withNoStepDescriptions( - "feature name", - mock(RunnerSupplier.class), - pickles.get(0), - createJUnitOptions() + "feature name", + mock(RunnerSupplier.class), + pickles.get(0), + createJunitOptions() ); assertEquals("scenario name(feature name)", runner.getDescription().getDisplayName()); } @Test - public void shouldConvertTextFromFeatureFileForNamesWithFilenameCompatibleNameOption() throws Exception { + public void shouldConvertTextFromFeatureFileForNamesWithFilenameCompatibleNameOption() { List pickles = TestPickleBuilder.pickleEventsFromFeature("featurePath", "" + - "Feature: feature name\n" + - " Scenario: scenario name\n" + - " Then it works\n"); + "Feature: feature name\n" + + " Scenario: scenario name\n" + + " Then it works\n"); PickleRunner runner = PickleRunners.withNoStepDescriptions( - "feature name", - mock(RunnerSupplier.class), - pickles.get(0), - createJUnitOptions("--filename-compatible-names") + "feature name", + mock(RunnerSupplier.class), + pickles.get(0), + createFileNameCompatibleJUnitOptions() ); assertEquals("scenario_name(feature_name)", runner.getDescription().getDisplayName()); } @Test - public void shouldConvertTextFromFeatureFileWithRussianLanguage() throws Exception { + public void shouldConvertTextFromFeatureFileWithRussianLanguage() { List pickles = TestPickleBuilder.pickleEventsFromFeature("featurePath", "" + "#language:ru\n" + "ФункциÑ: Ð¸Ð¼Ñ Ñ„ÑƒÐ½ÐºÑ†Ð¸Ð¸\n" + @@ -60,17 +58,17 @@ public void shouldConvertTextFromFeatureFileWithRussianLanguage() throws Excepti "Ð¸Ð¼Ñ Ñ„ÑƒÐ½ÐºÑ†Ð¸Ð¸", mock(RunnerSupplier.class), pickles.get(0), - createJUnitOptions("--filename-compatible-names") + createFileNameCompatibleJUnitOptions() ); assertEquals("____________(___________)", runner.getDescription().getDisplayName()); } - private JUnitOptions createJUnitOptions() { - return new JUnitOptionsParser().parse(Collections.emptyList()).setStrict(true).build(); + private JUnitOptions createJunitOptions() { + return new JUnitOptionsBuilder().setStrict(true).build(); } - private JUnitOptions createJUnitOptions(String option) { - return new JUnitOptionsParser().parse(Collections.singletonList(option)).setStrict(true).build(); + private JUnitOptions createFileNameCompatibleJUnitOptions() { + return new JUnitOptionsBuilder().setFilenameCompatibleNames(true).setStrict(true).build(); } } diff --git a/junit/src/test/java/io/cucumber/junit/PickleRunnerWithStepDescriptionsTest.java b/junit/src/test/java/io/cucumber/junit/PickleRunnerWithStepDescriptionsTest.java index e854bfd82c..48c9a8fa04 100644 --- a/junit/src/test/java/io/cucumber/junit/PickleRunnerWithStepDescriptionsTest.java +++ b/junit/src/test/java/io/cucumber/junit/PickleRunnerWithStepDescriptionsTest.java @@ -1,13 +1,12 @@ package io.cucumber.junit; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; +import static org.junit.Assert.*; import static org.mockito.Mockito.mock; -import cucumber.runner.RunnerSupplier; -import cucumber.runtime.model.CucumberFeature; +import io.cucumber.core.runtime.RunnerSupplier; import io.cucumber.junit.PickleRunners.PickleRunner; import io.cucumber.junit.PickleRunners.WithStepDescriptions; +import io.cucumber.core.feature.CucumberFeature; import gherkin.events.PickleEvent; import gherkin.pickles.Compiler; import gherkin.pickles.Pickle; @@ -16,8 +15,6 @@ import org.junit.runner.Description; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.List; public class PickleRunnerWithStepDescriptionsTest { @@ -40,12 +37,12 @@ public void shouldAssignUnequalDescriptionsToDifferentOccurrencesOfSameStepInASc List pickleEvents = new ArrayList<>(); for (Pickle pickle : compiler.compile(features.getGherkinFeature())) { pickleEvents.add(new PickleEvent(features.getUri().toString(), pickle)); - }; + } WithStepDescriptions runner = (WithStepDescriptions) PickleRunners.withStepDescriptions( - mock(RunnerSupplier.class), - pickleEvents.get(0), - createJUnitOptions() + mock(RunnerSupplier.class), + pickleEvents.get(0), + createJunitOptions() ); // fish out the two occurrences of the same step and check whether we really got them @@ -59,7 +56,7 @@ public void shouldAssignUnequalDescriptionsToDifferentOccurrencesOfSameStepInASc Description stepDescription1 = runnerDescription.getChildren().get(0); Description stepDescription2 = runnerDescription.getChildren().get(2); - assertFalse("Descriptions must not be equal.", stepDescription1.equals(stepDescription2)); + assertNotEquals("Descriptions must not be equal.", stepDescription1, stepDescription2); } @Test @@ -75,16 +72,16 @@ public void shouldAssignUnequalDescriptionsToDifferentStepsInAScenarioOutline() ); WithStepDescriptions runner = (WithStepDescriptions) PickleRunners.withStepDescriptions( - mock(RunnerSupplier.class), - features.getPickles().get(0), - createJUnitOptions() + mock(RunnerSupplier.class), + features.getPickles().get(0), + createJunitOptions() ); Description runnerDescription = runner.getDescription(); Description stepDescription1 = runnerDescription.getChildren().get(0); Description stepDescription2 = runnerDescription.getChildren().get(1); - assertFalse("Descriptions must not be equal.", stepDescription1.equals(stepDescription2)); + assertNotEquals("Descriptions must not be equal.", stepDescription1, stepDescription2); } @Test @@ -101,9 +98,9 @@ public void shouldIncludeScenarioNameAsClassNameInStepDescriptions() throws Exce ); PickleRunner runner = PickleRunners.withStepDescriptions( - mock(RunnerSupplier.class), - features.getPickles().get(0), - createJUnitOptions() + mock(RunnerSupplier.class), + features.getPickles().get(0), + createJunitOptions() ); // fish out the data from runner @@ -119,14 +116,14 @@ public void shouldIncludeScenarioNameAsClassNameInStepDescriptions() throws Exce @Test public void shouldUseScenarioNameForDisplayName() throws Exception { List pickles = TestPickleBuilder.pickleEventsFromFeature("featurePath", "" + - "Feature: feature name\n" + - " Scenario: scenario name\n" + - " Then it works\n"); + "Feature: feature name\n" + + " Scenario: scenario name\n" + + " Then it works\n"); PickleRunner runner = PickleRunners.withStepDescriptions( - mock(RunnerSupplier.class), - pickles.get(0), - createJUnitOptions() + mock(RunnerSupplier.class), + pickles.get(0), + createJunitOptions() ); assertEquals("scenario name", runner.getDescription().getDisplayName()); @@ -135,14 +132,14 @@ public void shouldUseScenarioNameForDisplayName() throws Exception { @Test public void shouldUseStepKeyworkAndNameForChildName() throws Exception { List pickleEvents = TestPickleBuilder.pickleEventsFromFeature("featurePath", "" + - "Feature: feature name\n" + - " Scenario: scenario name\n" + - " Then it works\n"); + "Feature: feature name\n" + + " Scenario: scenario name\n" + + " Then it works\n"); PickleRunner runner = PickleRunners.withStepDescriptions( - mock(RunnerSupplier.class), - pickleEvents.get(0), - createJUnitOptions() + mock(RunnerSupplier.class), + pickleEvents.get(0), + createJunitOptions() ); assertEquals("it works", runner.getDescription().getChildren().get(0).getMethodName()); @@ -151,14 +148,14 @@ public void shouldUseStepKeyworkAndNameForChildName() throws Exception { @Test public void shouldConvertTextFromFeatureFileForNamesWithFilenameCompatibleNameOption() throws Exception { List pickles = TestPickleBuilder.pickleEventsFromFeature("featurePath", "" + - "Feature: feature name\n" + - " Scenario: scenario name\n" + - " Then it works\n"); + "Feature: feature name\n" + + " Scenario: scenario name\n" + + " Then it works\n"); PickleRunner runner = PickleRunners.withStepDescriptions( - mock(RunnerSupplier.class), - pickles.get(0), - createJunitOptions("--filename-compatible-names") + mock(RunnerSupplier.class), + pickles.get(0), + createFileNameCompatibleJunitOptions() ); assertEquals("scenario_name", runner.getDescription().getDisplayName()); @@ -166,11 +163,12 @@ public void shouldConvertTextFromFeatureFileForNamesWithFilenameCompatibleNameOp assertEquals("it_works", runner.getDescription().getChildren().get(0).getMethodName()); } - private JUnitOptions createJUnitOptions() { + private JUnitOptions createJunitOptions() { return new JUnitOptionsBuilder().setStrict(true).build(); } - private JUnitOptions createJunitOptions(String option) { - return new JUnitOptionsParser().parse(Arrays.asList(option)).setStrict(true).build(); + private JUnitOptions createFileNameCompatibleJunitOptions() { + return new JUnitOptionsBuilder().setFilenameCompatibleNames(true).setStrict(true).build(); } + } diff --git a/junit/src/test/java/io/cucumber/junit/RunCucumberTestNoStepNotifications.java b/junit/src/test/java/io/cucumber/junit/RunCucumberTestNoStepNotifications.java index 6f7f880292..55b27d7059 100644 --- a/junit/src/test/java/io/cucumber/junit/RunCucumberTestNoStepNotifications.java +++ b/junit/src/test/java/io/cucumber/junit/RunCucumberTestNoStepNotifications.java @@ -3,6 +3,6 @@ import org.junit.runner.RunWith; @RunWith(Cucumber.class) -@CucumberOptions(junit = "--no-step-notifications") +@CucumberOptions(stepNotifications = false) public class RunCucumberTestNoStepNotifications { } diff --git a/junit/src/test/java/io/cucumber/junit/StubBackendProviderService.java b/junit/src/test/java/io/cucumber/junit/StubBackendProviderService.java new file mode 100644 index 0000000000..17d6f5e50d --- /dev/null +++ b/junit/src/test/java/io/cucumber/junit/StubBackendProviderService.java @@ -0,0 +1,71 @@ +package io.cucumber.junit; + + +import io.cucumber.core.backend.Backend; +import io.cucumber.core.backend.BackendProviderService; +import io.cucumber.core.backend.Container; +import io.cucumber.core.backend.Glue; +import io.cucumber.core.backend.Lookup; +import io.cucumber.core.io.ResourceLoader; +import io.cucumber.core.snippets.Snippet; + +import java.lang.reflect.Type; +import java.net.URI; +import java.text.MessageFormat; +import java.util.List; +import java.util.Map; + +public class StubBackendProviderService implements BackendProviderService { + + @Override + public Backend create(Lookup lookup, Container container, ResourceLoader resourceLoader) { + return new StubBackend(); + } + + /** + * We need an implementation of Backend to prevent Runtime from blowing up. + */ + public static class StubBackend implements Backend { + StubBackend() { + + } + + @Override + public void loadGlue(Glue glue, List gluePaths) { + } + + @Override + public void buildWorld() { + } + + @Override + public void disposeWorld() { + } + + @Override + public Snippet getSnippet() { + return new Snippet() { + @Override + public MessageFormat template() { + return new MessageFormat(""); + } + + @Override + public String tableHint() { + return ""; + } + + @Override + public String arguments(Map arguments) { + return ""; + } + + @Override + public String escapePattern(String pattern) { + return ""; + } + }; + } + } + +} diff --git a/junit/src/test/java/io/cucumber/junit/TestPendingException.java b/junit/src/test/java/io/cucumber/junit/TestPendingException.java index efa9582408..eaa5ca54ac 100644 --- a/junit/src/test/java/io/cucumber/junit/TestPendingException.java +++ b/junit/src/test/java/io/cucumber/junit/TestPendingException.java @@ -1,6 +1,6 @@ package io.cucumber.junit; -import cucumber.api.Pending; +import io.cucumber.core.backend.Pending; @Pending public final class TestPendingException extends RuntimeException { diff --git a/junit/src/test/java/io/cucumber/junit/TestPickleBuilder.java b/junit/src/test/java/io/cucumber/junit/TestPickleBuilder.java index d831fea3dc..c4188754c1 100644 --- a/junit/src/test/java/io/cucumber/junit/TestPickleBuilder.java +++ b/junit/src/test/java/io/cucumber/junit/TestPickleBuilder.java @@ -1,11 +1,11 @@ package io.cucumber.junit; -import cucumber.runtime.io.Resource; -import cucumber.runtime.model.CucumberFeature; -import cucumber.runtime.model.FeatureParser; +import io.cucumber.core.feature.FeatureParser; import gherkin.events.PickleEvent; import gherkin.pickles.Compiler; import gherkin.pickles.Pickle; +import io.cucumber.core.io.Resource; +import io.cucumber.core.feature.CucumberFeature; import java.io.ByteArrayInputStream; import java.io.InputStream; diff --git a/junit/src/test/resources/META-INF/services/io.cucumber.core.backend.BackendProviderService b/junit/src/test/resources/META-INF/services/io.cucumber.core.backend.BackendProviderService new file mode 100644 index 0000000000..86415bfe75 --- /dev/null +++ b/junit/src/test/resources/META-INF/services/io.cucumber.core.backend.BackendProviderService @@ -0,0 +1 @@ +io.cucumber.junit.StubBackendProviderService \ No newline at end of file diff --git a/kotlin-java8/pom.xml b/kotlin-java8/pom.xml index 3026c8cbf1..ff8568b85b 100644 --- a/kotlin-java8/pom.xml +++ b/kotlin-java8/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 4.7.2-SNAPSHOT + 5.0.0-SNAPSHOT cucumber-kotlin-java8 diff --git a/kotlin-java8/src/test/kotlin/cucumber/runtime/kotlin/test/LambdaStepdefs.kt b/kotlin-java8/src/test/kotlin/io/cucumber/kotlin/LambdaStepdefs.kt similarity index 95% rename from kotlin-java8/src/test/kotlin/cucumber/runtime/kotlin/test/LambdaStepdefs.kt rename to kotlin-java8/src/test/kotlin/io/cucumber/kotlin/LambdaStepdefs.kt index de1884da4c..b58170dfa4 100644 --- a/kotlin-java8/src/test/kotlin/cucumber/runtime/kotlin/test/LambdaStepdefs.kt +++ b/kotlin-java8/src/test/kotlin/io/cucumber/kotlin/LambdaStepdefs.kt @@ -1,8 +1,8 @@ -package cucumber.runtime.kotlin.test; +package io.cucumber.kotlin -import cucumber.api.Scenario +import io.cucumber.core.api.Scenario import io.cucumber.datatable.DataTable -import cucumber.api.java8.En +import io.cucumber.java8.En import org.junit.Assert.* var lastInstance : LambdaStepdefs? = null diff --git a/kotlin-java8/src/test/kotlin/cucumber/runtime/kotlin/test/RunCukesTest.kt b/kotlin-java8/src/test/kotlin/io/cucumber/kotlin/RunCukesTest.kt similarity index 51% rename from kotlin-java8/src/test/kotlin/cucumber/runtime/kotlin/test/RunCukesTest.kt rename to kotlin-java8/src/test/kotlin/io/cucumber/kotlin/RunCukesTest.kt index f832bce8c3..030c347b4f 100644 --- a/kotlin-java8/src/test/kotlin/cucumber/runtime/kotlin/test/RunCukesTest.kt +++ b/kotlin-java8/src/test/kotlin/io/cucumber/kotlin/RunCukesTest.kt @@ -1,6 +1,6 @@ -package cucumber.runtime.kotlin.test +package io.cucumber.runtime.kotlin.test -import cucumber.api.junit.Cucumber +import io.cucumber.junit.Cucumber import org.junit.runner.RunWith @RunWith(Cucumber::class) diff --git a/kotlin-java8/src/test/kotlin/cucumber/runtime/kotlin/test/TypeRegistryConfiguration.kt b/kotlin-java8/src/test/kotlin/io/cucumber/kotlin/TypeRegistryConfiguration.kt similarity index 78% rename from kotlin-java8/src/test/kotlin/cucumber/runtime/kotlin/test/TypeRegistryConfiguration.kt rename to kotlin-java8/src/test/kotlin/io/cucumber/kotlin/TypeRegistryConfiguration.kt index 8ac7ef678b..a11e86bdc9 100644 --- a/kotlin-java8/src/test/kotlin/cucumber/runtime/kotlin/test/TypeRegistryConfiguration.kt +++ b/kotlin-java8/src/test/kotlin/io/cucumber/kotlin/TypeRegistryConfiguration.kt @@ -1,9 +1,10 @@ -package cucumber.runtime.kotlin.test +package io.cucumber.runtime.kotlin.test -import cucumber.api.TypeRegistryConfigurer -import cucumber.api.TypeRegistry +import io.cucumber.core.api.TypeRegistryConfigurer +import io.cucumber.core.api.TypeRegistry import io.cucumber.datatable.DataTableType import io.cucumber.datatable.TableEntryTransformer +import io.cucumber.kotlin.Person import java.util.Locale import java.util.Locale.ENGLISH diff --git a/kotlin-java8/src/test/resources/cucumber/runtime/kotlin/test/kotlin.feature b/kotlin-java8/src/test/resources/io/cucumber/kotlin/kotlin.feature similarity index 100% rename from kotlin-java8/src/test/resources/cucumber/runtime/kotlin/test/kotlin.feature rename to kotlin-java8/src/test/resources/io/cucumber/kotlin/kotlin.feature diff --git a/needle/pom.xml b/needle/pom.xml index a3f26476bc..ac6f23ca59 100644 --- a/needle/pom.xml +++ b/needle/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 4.7.2-SNAPSHOT + 5.0.0-SNAPSHOT cucumber-needle @@ -34,8 +34,18 @@ test - junit - junit + org.hamcrest + hamcrest-library + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.vintage + junit-vintage-engine test diff --git a/needle/src/main/java/cucumber/api/needle/InjectionProviderInstancesSupplier.java b/needle/src/main/java/cucumber/api/needle/InjectionProviderInstancesSupplier.java deleted file mode 100644 index 976951e442..0000000000 --- a/needle/src/main/java/cucumber/api/needle/InjectionProviderInstancesSupplier.java +++ /dev/null @@ -1,11 +0,0 @@ -package cucumber.api.needle; - -/** - * Supplies a Set of - * InjectionProvider instances that are created outside the {@link io.cucumber.needle.NeedleFactory} lifecycle. - * @deprecated use {@code io.cucumber.needle.api.InjectionProviderInstancesSupplier} instead - */ -@Deprecated -public interface InjectionProviderInstancesSupplier extends io.cucumber.needle.InjectionProviderInstancesSupplier { - -} diff --git a/needle/src/main/java/cucumber/api/needle/NeedleInjectionProvider.java b/needle/src/main/java/cucumber/api/needle/NeedleInjectionProvider.java deleted file mode 100755 index 4052de8f50..0000000000 --- a/needle/src/main/java/cucumber/api/needle/NeedleInjectionProvider.java +++ /dev/null @@ -1,18 +0,0 @@ -package cucumber.api.needle; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotation to mark InjectionProviders in the cucumber glue or cucumber steps. - * Should be placed on fields of type {@link de.akquinet.jbosscc.needle.injection.InjectionProvider} or an array of those. - * - * @deprecated use {@code NeedleInjectionProvider} instead - */ -@Target({ElementType.FIELD}) -@Retention(RetentionPolicy.RUNTIME) -@Deprecated -public @interface NeedleInjectionProvider { -} diff --git a/needle/src/main/java/cucumber/api/needle/package.html b/needle/src/main/java/cucumber/api/needle/package.html deleted file mode 100644 index 53513df03a..0000000000 --- a/needle/src/main/java/cucumber/api/needle/package.html +++ /dev/null @@ -1,10 +0,0 @@ - -

- By including the cucumber-needle jar - on your CLASSPATH your Glue classes will be instantiated - by needle. - - You can use the InjectionProviderInstanceSupplier and NeedleInjectionProvider to configure the behavior of the - needle injection. -

- diff --git a/needle/src/main/java/io/cucumber/needle/CollectInjectionProvidersFromStepsInstance.java b/needle/src/main/java/io/cucumber/needle/CollectInjectionProvidersFromStepsInstance.java index cb7c93ae87..348d097ee4 100755 --- a/needle/src/main/java/io/cucumber/needle/CollectInjectionProvidersFromStepsInstance.java +++ b/needle/src/main/java/io/cucumber/needle/CollectInjectionProvidersFromStepsInstance.java @@ -31,41 +31,30 @@ enum CollectInjectionProvidersFromStepsInstance { * @return collected injection providers. */ final InjectionProvider[] apply(final T instance) { - final Set> allProviders = new LinkedHashSet<>(); + final Set> providers = new LinkedHashSet<>(); for (final Field field : ReflectionUtil.getAllFieldsWithAnnotation(instance, NeedleInjectionProvider.class)) { - final Set> providers = getInjectionProviders(field, instance); - allProviders.addAll(providers); - } - for (final Field field : ReflectionUtil.getAllFieldsWithAnnotation(instance, cucumber.api.needle.NeedleInjectionProvider.class)) { - final Set> providers = getInjectionProviders(field, instance); - allProviders.addAll(providers); + field.setAccessible(true); + try { + final Object value = field.get(instance); + if (value instanceof InjectionProvider[]) { + providers.addAll(Arrays.asList((InjectionProvider[]) value)); + } else if (value instanceof InjectionProvider) { + providers.add((InjectionProvider) value); + } else if (value instanceof InjectionProviderInstancesSupplier) { + providers.addAll(((InjectionProviderInstancesSupplier) value).get()); + } else { + throw new IllegalStateException("Fields annotated with NeedleInjectionProviders must be of type " + + "InjectionProviderInstancesSupplier, InjectionProvider " + "or InjectionProvider[]"); + } + } catch (final Exception e) { + throw new IllegalStateException(e); + } } if (logger.isTraceEnabled()) { - logger.trace("Adding {} InjectionProvider instances.", allProviders.size()); + logger.trace("Adding {} InjectionProvider instances.", providers.size()); } - return allProviders.toArray(new InjectionProvider[0]); - } - - private Set> getInjectionProviders(Field field, T instance) { - final Set> providers = new LinkedHashSet<>(); - field.setAccessible(true); - try { - final Object value = field.get(instance); - if (value instanceof InjectionProvider[]) { - providers.addAll(Arrays.asList((InjectionProvider[]) value)); - } else if (value instanceof InjectionProvider) { - providers.add((InjectionProvider) value); - } else if (value instanceof InjectionProviderInstancesSupplier) { - providers.addAll(((InjectionProviderInstancesSupplier) value).get()); - } else { - throw new IllegalStateException("Fields annotated with NeedleInjectionProviders must be of type " - + "InjectionProviderInstancesSupplier, InjectionProvider " + "or InjectionProvider[]"); - } - } catch (final Exception e) { - throw new IllegalStateException(e); - } - return providers; + return providers.toArray(new InjectionProvider[0]); } } diff --git a/needle/src/main/java/io/cucumber/needle/CucumberNeedleConfiguration.java b/needle/src/main/java/io/cucumber/needle/CucumberNeedleConfiguration.java index e7cbe6c249..a80025dcc1 100644 --- a/needle/src/main/java/io/cucumber/needle/CucumberNeedleConfiguration.java +++ b/needle/src/main/java/io/cucumber/needle/CucumberNeedleConfiguration.java @@ -36,7 +36,7 @@ class CucumberNeedleConfiguration { injectionProviders.add((InjectionProvider) createInstance.apply(clazz)); } else if (isInjectionProviderInstanceSupplier(clazz)) { final InjectionProviderInstancesSupplier supplier = (InjectionProviderInstancesSupplier) createInstance - .apply(clazz); + .apply(clazz); final Set> providers = supplier.get(); if (providers != null) { injectionProviders.addAll(providers); @@ -48,10 +48,6 @@ class CucumberNeedleConfiguration { } } - InjectionProvider[] getInjectionProviders() { - return injectionProviders.toArray(new InjectionProvider[0]); - } - /** * Checks if given class is an {@link InjectionProvider} * @@ -71,4 +67,8 @@ static boolean isInjectionProvider(final Class type) { static boolean isInjectionProviderInstanceSupplier(final Class type) { return InjectionProviderInstancesSupplier.class.isAssignableFrom(type); } + + InjectionProvider[] getInjectionProviders() { + return injectionProviders.toArray(new InjectionProvider[0]); + } } diff --git a/needle/src/test/java/io/cucumber/needle/CreateInstanceByDefaultConstructorTest.java b/needle/src/test/java/io/cucumber/needle/CreateInstanceByDefaultConstructorTest.java index e45b683a77..1d5f7317eb 100644 --- a/needle/src/test/java/io/cucumber/needle/CreateInstanceByDefaultConstructorTest.java +++ b/needle/src/test/java/io/cucumber/needle/CreateInstanceByDefaultConstructorTest.java @@ -1,8 +1,13 @@ package io.cucumber.needle; -import org.junit.Test; - +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.Test; +import org.junit.jupiter.api.function.Executable; public class CreateInstanceByDefaultConstructorTest { @@ -23,9 +28,11 @@ public void shouldCreateNewInstance() { assertNotNull(createInstanceByDefaultConstructor.apply(HasDefaultConstructor.class)); } - @Test(expected = IllegalStateException.class) + @Test public void shouldNotCreateNewInstanceWhenConstructorIsMissing() { - createInstanceByDefaultConstructor.apply(DoesNotHaveDefaultConstructor.class); + final Executable testMethod = () -> createInstanceByDefaultConstructor.apply(DoesNotHaveDefaultConstructor.class); + final IllegalStateException expectedThrown = assertThrows(IllegalStateException.class, testMethod); + assertThat(expectedThrown.getMessage(), is(equalTo("Can not instantiate Instance by Default Constructor."))); } } diff --git a/needle/src/test/java/io/cucumber/needle/CucumberNeedleConfigurationTest.java b/needle/src/test/java/io/cucumber/needle/CucumberNeedleConfigurationTest.java index d2296497d4..24d0176f5f 100644 --- a/needle/src/test/java/io/cucumber/needle/CucumberNeedleConfigurationTest.java +++ b/needle/src/test/java/io/cucumber/needle/CucumberNeedleConfigurationTest.java @@ -12,6 +12,9 @@ public class CucumberNeedleConfigurationTest { + public abstract static class A implements InjectionProviderInstancesSupplier { + } + @Test public void shouldReturnEmptyInstances() { final InjectionProvider[] allInjectionProviders = new CucumberNeedleConfiguration("resource-bundles/empty") @@ -27,7 +30,4 @@ public void shouldEvaluateIfTypeIsInjectionProviderOrSupplier() throws Exception assertFalse(CucumberNeedleConfiguration.isInjectionProvider(A.class)); assertTrue(CucumberNeedleConfiguration.isInjectionProviderInstanceSupplier(A.class)); } - - public abstract static class A implements InjectionProviderInstancesSupplier { - } } diff --git a/needle/src/test/java/io/cucumber/needle/LoadCucumberNeedleResourceBundleTest.java b/needle/src/test/java/io/cucumber/needle/LoadCucumberNeedleResourceBundleTest.java index 7aaad27a72..292dbb883d 100644 --- a/needle/src/test/java/io/cucumber/needle/LoadCucumberNeedleResourceBundleTest.java +++ b/needle/src/test/java/io/cucumber/needle/LoadCucumberNeedleResourceBundleTest.java @@ -1,15 +1,18 @@ package io.cucumber.needle; -import org.hamcrest.CoreMatchers; -import org.junit.Test; - -import java.util.ResourceBundle; - import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ResourceBundle; + +import org.hamcrest.CoreMatchers; +import org.junit.Test; +import org.junit.jupiter.api.function.Executable; public class LoadCucumberNeedleResourceBundleTest { @@ -23,14 +26,14 @@ public void shouldReturnEmptyResourceBundleWhenResourceDoesNotExist() throws Exc } @Test - public void shouldReturnExistingResourceBundle() throws Exception { + public void shouldReturnExistingResourceBundle() { final ResourceBundle resourceBundle = function.apply("empty"); assertNotNull(resourceBundle); assertTrue(resourceBundle.keySet().isEmpty()); } @Test - public void shouldAlwaysReturnEmptyForEmptyResourceBundle() throws Exception { + public void shouldAlwaysReturnEmptyForEmptyResourceBundle() { final ResourceBundle resourceBundle = LoadResourceBundle.EMPTY_RESOURCE_BUNDLE; assertNotNull(resourceBundle.getObject("foo")); @@ -38,19 +41,25 @@ public void shouldAlwaysReturnEmptyForEmptyResourceBundle() throws Exception { assertFalse(resourceBundle.getKeys().hasMoreElements()); } - @Test(expected = IllegalArgumentException.class) + @Test public void shouldFailWhenResourceNameIsNull() { - function.apply(null); + final Executable testMethod = () -> function.apply(null); + final IllegalArgumentException expectedThrown = assertThrows(IllegalArgumentException.class, testMethod); + assertThat(expectedThrown.getMessage(), is(equalTo("resourceName must not be null or empty!"))); } - @Test(expected = IllegalArgumentException.class) + @Test public void shouldFailWhenResourceNameIsEmpty() { - function.apply(""); + final Executable testMethod = () -> function.apply(""); + final IllegalArgumentException expectedThrown = assertThrows(IllegalArgumentException.class, testMethod); + assertThat(expectedThrown.getMessage(), is(equalTo("resourceName must not be null or empty!"))); } - @Test(expected = IllegalArgumentException.class) + @Test public void shouldFailWhenResourceNameIsBlank() { - function.apply(" "); + final Executable testMethod = () -> function.apply(" "); + final IllegalArgumentException expectedThrown = assertThrows(IllegalArgumentException.class, testMethod); + assertThat(expectedThrown.getMessage(), is(equalTo("resourceName must not be null or empty!"))); } } diff --git a/needle/src/test/java/io/cucumber/needle/NeedleFactoryTest.java b/needle/src/test/java/io/cucumber/needle/NeedleFactoryTest.java index 7687094c49..406333ab35 100644 --- a/needle/src/test/java/io/cucumber/needle/NeedleFactoryTest.java +++ b/needle/src/test/java/io/cucumber/needle/NeedleFactoryTest.java @@ -12,7 +12,7 @@ public class NeedleFactoryTest { @Test - public void shouldSetUpInjectionProviders() throws Exception { + public void shouldSetUpInjectionProviders() { final InjectionProvider[] injectionProviders = NeedleFactory .setUpInjectionProviders(); diff --git a/needle/src/test/java/io/cucumber/needle/ReadInjectionProviderClassNamesTest.java b/needle/src/test/java/io/cucumber/needle/ReadInjectionProviderClassNamesTest.java index ddb61018ad..99424626e7 100644 --- a/needle/src/test/java/io/cucumber/needle/ReadInjectionProviderClassNamesTest.java +++ b/needle/src/test/java/io/cucumber/needle/ReadInjectionProviderClassNamesTest.java @@ -1,17 +1,15 @@ package io.cucumber.needle; -import static io.cucumber.needle.CucumberNeedleConfiguration.RESOURCE_CUCUMBER_NEEDLE; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.core.IsCollectionContaining.hasItems; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; +import io.cucumber.needle.test.injectionprovider.SimpleNameGetterProvider; +import org.junit.Test; import java.util.ResourceBundle; import java.util.Set; -import org.junit.Test; - -import io.cucumber.needle.test.injectionprovider.SimpleNameGetterProvider; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.core.IsCollectionContaining.hasItems; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; public class ReadInjectionProviderClassNamesTest { @@ -19,7 +17,7 @@ public class ReadInjectionProviderClassNamesTest { @Test public void shouldReturnProviderFromCucumberNeedleProperties() { - final Set classNames = function.apply(loadBundle(RESOURCE_CUCUMBER_NEEDLE)); + final Set classNames = function.apply(loadBundle(CucumberNeedleConfiguration.RESOURCE_CUCUMBER_NEEDLE)); assertNotNull(classNames); assertThat(classNames.size(), is(1)); assertThat(classNames.iterator().next(), is(SimpleNameGetterProvider.class.getCanonicalName())); @@ -47,7 +45,7 @@ public void shouldReturnEmptySetWhenPropertyIsEmpty() { } @Test - public void shouldReturnOneTrimmedClassName() throws Exception { + public void shouldReturnOneTrimmedClassName() { final Set classNames = function.apply(loadBundle("resource-bundles/one-classname")); assertThat(classNames.size(), is(1)); final String first = classNames.iterator().next(); @@ -55,7 +53,7 @@ public void shouldReturnOneTrimmedClassName() throws Exception { } @Test - public void shouldReturnTwoTrimmedClassNames() throws Exception { + public void shouldReturnTwoTrimmedClassNames() { final Set classNames = function.apply(loadBundle("resource-bundles/two-classname")); assertThat(classNames.size(), is(2)); assertThat(classNames, hasItems("java.lang.String", "java.util.Set")); diff --git a/needle/src/test/java/io/cucumber/needle/test/AtmWithdrawalGlue.java b/needle/src/test/java/io/cucumber/needle/test/AtmWithdrawalGlue.java index 4662d9456b..736b97c97e 100755 --- a/needle/src/test/java/io/cucumber/needle/test/AtmWithdrawalGlue.java +++ b/needle/src/test/java/io/cucumber/needle/test/AtmWithdrawalGlue.java @@ -5,7 +5,7 @@ import javax.inject.Inject; -import cucumber.api.java.Before; +import io.cucumber.java.Before; import io.cucumber.needle.test.injectionprovider.NameGetter; import io.cucumber.needle.test.injectionprovider.SimpleNameGetterProvider; diff --git a/needle/src/test/java/io/cucumber/needle/test/AtmWithdrawalSteps.java b/needle/src/test/java/io/cucumber/needle/test/AtmWithdrawalSteps.java index 3f90073a65..2dfec1e0e0 100755 --- a/needle/src/test/java/io/cucumber/needle/test/AtmWithdrawalSteps.java +++ b/needle/src/test/java/io/cucumber/needle/test/AtmWithdrawalSteps.java @@ -1,30 +1,29 @@ package io.cucumber.needle.test; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.when; - -import cucumber.api.java.Before; -import cucumber.api.java.en.Given; -import cucumber.api.java.en.Then; -import cucumber.api.java.en.When; +import de.akquinet.jbosscc.needle.annotation.ObjectUnderTest; +import de.akquinet.jbosscc.needle.injection.InjectionProvider; +import io.cucumber.java.Before; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +import io.cucumber.needle.DefaultInstanceInjectionProvider; import io.cucumber.needle.InjectionProviderInstancesSupplier; import io.cucumber.needle.NeedleInjectionProvider; -import io.cucumber.needle.DefaultInstanceInjectionProvider; import io.cucumber.needle.test.atm.AtmService; import io.cucumber.needle.test.atm.AtmServiceBean; import io.cucumber.needle.test.atm.BicGetter; import io.cucumber.needle.test.injectionprovider.ValueInjectionProvider; -import de.akquinet.jbosscc.needle.annotation.ObjectUnderTest; -import de.akquinet.jbosscc.needle.injection.InjectionProvider; import org.hamcrest.core.Is; +import javax.inject.Inject; import java.util.Collections; import java.util.Set; -import javax.inject.Inject; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; public class AtmWithdrawalSteps { diff --git a/needle/src/test/java/io/cucumber/needle/test/MoreSteps.java b/needle/src/test/java/io/cucumber/needle/test/MoreSteps.java index df4b7012e4..f2df1e54d0 100644 --- a/needle/src/test/java/io/cucumber/needle/test/MoreSteps.java +++ b/needle/src/test/java/io/cucumber/needle/test/MoreSteps.java @@ -1,8 +1,8 @@ package io.cucumber.needle.test; -import cucumber.api.java.Before; -import cucumber.api.java.en.Given; import de.akquinet.jbosscc.needle.injection.InjectionProvider; +import io.cucumber.java.Before; +import io.cucumber.java.en.Given; import io.cucumber.needle.DefaultInstanceInjectionProvider; import io.cucumber.needle.NeedleInjectionProvider; diff --git a/openejb/pom.xml b/openejb/pom.xml index 67948e601c..d64eccbf08 100644 --- a/openejb/pom.xml +++ b/openejb/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 4.7.2-SNAPSHOT + 5.0.0-SNAPSHOT cucumber-openejb diff --git a/openejb/src/main/java/io/cucumber/openejb/OpenEJBObjectFactory.java b/openejb/src/main/java/io/cucumber/openejb/OpenEJBObjectFactory.java index 49477ac731..975e7f0a37 100644 --- a/openejb/src/main/java/io/cucumber/openejb/OpenEJBObjectFactory.java +++ b/openejb/src/main/java/io/cucumber/openejb/OpenEJBObjectFactory.java @@ -1,6 +1,6 @@ package io.cucumber.openejb; -import cucumber.runtime.CucumberException; +import io.cucumber.core.exception.CucumberException; import io.cucumber.core.backend.ObjectFactory; import org.apache.openejb.OpenEjbContainer; import org.apiguardian.api.API; diff --git a/openejb/src/test/java/io/cucumber/openejb/BellyStepdefs.java b/openejb/src/test/java/io/cucumber/openejb/BellyStepdefs.java index 4e42281038..8e1e8852b3 100644 --- a/openejb/src/test/java/io/cucumber/openejb/BellyStepdefs.java +++ b/openejb/src/test/java/io/cucumber/openejb/BellyStepdefs.java @@ -1,7 +1,7 @@ package io.cucumber.openejb; -import cucumber.api.java.en.Given; -import cucumber.api.java.en.Then; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; import javax.inject.Inject; diff --git a/openejb/src/test/java/io/cucumber/openejb/UnusedGlue.java b/openejb/src/test/java/io/cucumber/openejb/UnusedGlue.java index 87f9e18d55..e0e1c410a4 100644 --- a/openejb/src/test/java/io/cucumber/openejb/UnusedGlue.java +++ b/openejb/src/test/java/io/cucumber/openejb/UnusedGlue.java @@ -1,7 +1,7 @@ package io.cucumber.openejb; -import cucumber.api.java.Before; -import cucumber.api.java.en.Given; +import io.cucumber.java.Before; +import io.cucumber.java.en.Given; public class UnusedGlue { public UnusedGlue() { diff --git a/picocontainer/pom.xml b/picocontainer/pom.xml index 7fca10fa1d..aeef65f248 100644 --- a/picocontainer/pom.xml +++ b/picocontainer/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 4.7.2-SNAPSHOT + 5.0.0-SNAPSHOT cucumber-picocontainer diff --git a/picocontainer/src/main/java/io/cucumber/picocontainer/PicoFactory.java b/picocontainer/src/main/java/io/cucumber/picocontainer/PicoFactory.java index 51baf81c06..880fec3af5 100644 --- a/picocontainer/src/main/java/io/cucumber/picocontainer/PicoFactory.java +++ b/picocontainer/src/main/java/io/cucumber/picocontainer/PicoFactory.java @@ -1,7 +1,7 @@ package io.cucumber.picocontainer; -import cucumber.runtime.Utils; import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.reflection.Reflections; import org.apiguardian.api.API; import org.picocontainer.MutablePicoContainer; import org.picocontainer.PicoBuilder; @@ -32,7 +32,7 @@ public void stop() { } public boolean addClass(Class clazz) { - if (Utils.isInstantiable(clazz) && classes.add(clazz)) { + if (Reflections.isInstantiable(clazz) && classes.add(clazz)) { addConstructorDependencies(clazz); } return true; diff --git a/picocontainer/src/test/java/io/cucumber/picocontainer/StepDefs.java b/picocontainer/src/test/java/io/cucumber/picocontainer/StepDefs.java index 4c80017940..e3b9e5293f 100644 --- a/picocontainer/src/test/java/io/cucumber/picocontainer/StepDefs.java +++ b/picocontainer/src/test/java/io/cucumber/picocontainer/StepDefs.java @@ -1,11 +1,11 @@ package io.cucumber.picocontainer; -import cucumber.api.PendingException; -import cucumber.api.Scenario; -import cucumber.api.java.After; -import cucumber.api.java.Before; -import cucumber.api.java.en.Given; -import cucumber.api.java.en.Then; +import io.cucumber.java.PendingException; +import io.cucumber.core.api.Scenario; +import io.cucumber.java.After; +import io.cucumber.java.Before; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; import java.util.Collections; import java.util.List; diff --git a/picocontainer/src/test/java/io/cucumber/picocontainer/UnusedGlue.java b/picocontainer/src/test/java/io/cucumber/picocontainer/UnusedGlue.java index ebdd900f52..0b453b4abb 100644 --- a/picocontainer/src/test/java/io/cucumber/picocontainer/UnusedGlue.java +++ b/picocontainer/src/test/java/io/cucumber/picocontainer/UnusedGlue.java @@ -1,7 +1,7 @@ package io.cucumber.picocontainer; -import cucumber.api.java.Before; -import cucumber.api.java.en.Given; +import io.cucumber.java.Before; +import io.cucumber.java.en.Given; public class UnusedGlue { diff --git a/pom.xml b/pom.xml index 553d6e34a0..294b5d8484 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ 1.0.4 cucumber-jvm - 4.7.2-SNAPSHOT + 5.0.0-SNAPSHOT pom Cucumber-JVM Cucumber for the JVM @@ -45,10 +45,11 @@ 7.0.2 1.1.14 1.1.1 - 0.2.7 4.12 + 5.3.1 + 1.3.1 1.3 3.12.2 2.28.2 @@ -115,32 +116,27 @@ io.cucumber - cucumber-spring + cucumber-java8 ${project.version} io.cucumber - cucumber-junit + cucumber-spring ${project.version} io.cucumber - cucumber-testng + cucumber-junit ${project.version} io.cucumber - cucumber-picocontainer + cucumber-testng ${project.version} io.cucumber - cucumber-html - ${cucumber-html.version} - - - io.cucumber - cucumber-java8 + cucumber-picocontainer ${project.version} @@ -164,7 +160,21 @@ hamcrest-library ${hamcrest.version} - + + org.junit.jupiter + junit-jupiter-api + ${junit-jupiter.version} + + + org.junit.jupiter + junit-jupiter-engine + ${junit-jupiter.version} + + + org.junit.vintage + junit-vintage-engine + ${junit-jupiter.version} + org.assertj assertj-core @@ -313,31 +323,78 @@ + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.2 + + UTF-8 + 1.8 + 1.8 + + + org.apache.maven.plugins maven-javadoc-plugin false - cucumber.runtime,cucumber.runtime.*,org.springframework - + io.cucumber.examples*,org.springframework - http://docs.oracle.com/javase/7/docs/api/ + http://docs.oracle.com/javase/8/docs/api/ http://junit.sourceforge.net/javadoc/ - Main API Packages - cucumber.api:cucumber.api.* + API Packages + + cucumber.api.*:io.cucumber.core.api:io.cucumber.core.api.*:io.cucumber.junit:io.cucumber.testng.api + - I18n - Java - cucumber.api.java.* + I18n + io.cucumber.java8.*:io.cucumber.java.* + + + Dependency Injection Providers + + io.cucumber.spring:io.cucumber.weld:io.cucumber.guice:io.cucumber.needle:io.cucumber.picocontainer:io.cucumber.openejb + java + + org.apache.maven.plugins + maven-surefire-plugin + + -Duser.language=en + -Xmx1024m + -XX:MaxPermSize=256m + -Dfile.encoding=UTF-8 + false + + + + org.junit.jupiter + junit-jupiter-engine + ${junit-jupiter.version} + + + org.junit.platform + junit-platform-engine + ${junit-platform.version} + + + org.junit.vintage + junit-vintage-engine + ${junit-jupiter.version} + + + + org.apache.maven.plugins maven-war-plugin @@ -373,42 +430,9 @@ - - true - - io\.cucumber\.core\.options\..* - io\.cucumber\.core\.model\..* - io\.cucumber\.junit\..* - io\.cucumber\.testng\..* - io\.cucumber\.needle\..* - cucumber\.api\.junit\..* - cucumber\.api\.testng\..* - cucumber\.api\.picocontainer\..* - cucumber\.api\.weld\..* - cucumber\.api\.openejb\..* - cucumber\.api\.needle\..* - cucumber\.runner\..* - cucumber\.runtime\..* - cucumber\.util\..* - - org\.springframework\..* - - true - io\.cucumber\.core\.options\..* - io\.cucumber\.junit\..* - io\.cucumber\.needle\..* - cucumber\.api\.junit\..* - cucumber\.api\.testng\..* - cucumber\.api\.picocontainer\..* - cucumber\.api\.weld\..* - cucumber\.api\.openejb\..* - cucumber\.api\.needle\..* - cucumber\.runner\..* - cucumber\.runtime\..* - cucumber\.util\..* org\.springframework\..* diff --git a/spring/pom.xml b/spring/pom.xml index 6e8b39c4f1..f174cda732 100644 --- a/spring/pom.xml +++ b/spring/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 4.7.2-SNAPSHOT + 5.0.0-SNAPSHOT cucumber-spring @@ -23,12 +23,6 @@ cucumber-java - - org.springframework - spring-tx - ${spring.version} - provided - org.springframework spring-context-support diff --git a/spring/src/main/java/cucumber/api/spring/SpringTransactionHooks.java b/spring/src/main/java/cucumber/api/spring/SpringTransactionHooks.java deleted file mode 100644 index 31774ef581..0000000000 --- a/spring/src/main/java/cucumber/api/spring/SpringTransactionHooks.java +++ /dev/null @@ -1,97 +0,0 @@ -package cucumber.api.spring; - -import cucumber.api.java.After; -import cucumber.api.java.Before; -import io.cucumber.core.logging.Logger; -import io.cucumber.core.logging.LoggerFactory; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.support.DefaultTransactionDefinition; -import org.springframework.transaction.support.SimpleTransactionStatus; - -/** - *

- * This class defines before and after hooks which provide automatic spring rollback capabilities. - * These hooks will apply to any element(s) within a .feature file tagged with @txn. - *

- *

- * Clients wishing to leverage these hooks should include this class' package in the glue code. - *

- *

- * The BEFORE and AFTER hooks (both with hook order 100) rely on being able to obtain a PlatformTransactionManager by type, or - * by an optionally specified bean name, from the runtime BeanFactory. - *

- *

- * NOTE: This class is NOT threadsafe! It relies on the fact that cucumber-jvm will instantiate an instance of any - * applicable hookdef class per scenario run. - *

- * - * @deprecated SpringTransactionHooks has been deprecated as it adds an unnecessary dependency on 'spring-tx'. - * Please implement your own transaction hooks if required. - */ -@Deprecated -public class SpringTransactionHooks implements BeanFactoryAware { - - private static final Logger log = LoggerFactory.getLogger(SpringTransactionHooks.class); - - private BeanFactory beanFactory; - private String txnManagerBeanName; - private TransactionStatus transactionStatus; - - public SpringTransactionHooks() { - log.warn( - "SpringTransactionHooks has been deprecated as it adds an unnecessary dependency on 'spring-tx'. " + - "Please implement your own transaction hooks if required." - ); - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - - /** - * @return the (optional) bean name for the transaction manager to be obtained - if null, attempt will be made to find a transaction manager by bean type - */ - public String getTxnManagerBeanName() { - return txnManagerBeanName; - } - - /** - * Setter to allow (optional) bean name to be specified for transaction manager bean - if null, attempt will be made to find a transaction manager by bean type - * - * @param txnManagerBeanName bean name of transaction manager bean - */ - public void setTxnManagerBeanName(String txnManagerBeanName) { - this.txnManagerBeanName = txnManagerBeanName; - } - - @Before(value = {"@txn"}, order = 100) - public void startTransaction() { - transactionStatus = obtainPlatformTransactionManager().getTransaction(new DefaultTransactionDefinition()); - } - - @After(value = {"@txn"}, order = 100) - public void rollBackTransaction() { - obtainPlatformTransactionManager().rollback(transactionStatus); - } - - public PlatformTransactionManager obtainPlatformTransactionManager() { - if (getTxnManagerBeanName() == null) { - return beanFactory.getBean(PlatformTransactionManager.class); - } else { - return beanFactory.getBean(txnManagerBeanName, PlatformTransactionManager.class); - } - } - - public TransactionStatus getTransactionStatus() { - return transactionStatus; - } - - public void setTransactionStatus(SimpleTransactionStatus transactionStatus) { - this.transactionStatus = transactionStatus; - } -} diff --git a/spring/src/main/java/cucumber/api/spring/package.html b/spring/src/main/java/cucumber/api/spring/package.html deleted file mode 100644 index 933747349d..0000000000 --- a/spring/src/main/java/cucumber/api/spring/package.html +++ /dev/null @@ -1,6 +0,0 @@ - -

- By including the cucumber-spring jar - on your CLASSPATH your Step Definitions will be instantiated by Spring. -

- diff --git a/spring/src/main/java/io/cucumber/spring/SpringFactory.java b/spring/src/main/java/io/cucumber/spring/SpringFactory.java index 636967d6f9..ade1407713 100644 --- a/spring/src/main/java/io/cucumber/spring/SpringFactory.java +++ b/spring/src/main/java/io/cucumber/spring/SpringFactory.java @@ -1,7 +1,7 @@ package io.cucumber.spring; -import cucumber.runtime.CucumberException; import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.exception.CucumberException; import org.apiguardian.api.API; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanDefinition; @@ -102,7 +102,7 @@ private static void checkNoComponentAnnotations(Class type) { } private static boolean hasComponentAnnotation(Annotation annotation) { - return hasAnnotation(annotation, Collections.>singleton(Component.class)); + return hasAnnotation(annotation, Collections.singleton(Component.class)); } private static boolean hasAnnotation(Annotation annotation, Collection> desired) { diff --git a/spring/src/test/java/io/cucumber/spring/SpringFactoryTest.java b/spring/src/test/java/io/cucumber/spring/SpringFactoryTest.java index dfaf906994..083d7c65c0 100644 --- a/spring/src/test/java/io/cucumber/spring/SpringFactoryTest.java +++ b/spring/src/test/java/io/cucumber/spring/SpringFactoryTest.java @@ -1,11 +1,10 @@ package io.cucumber.spring; -import cucumber.runtime.CucumberException; +import io.cucumber.core.exception.CucumberException; import io.cucumber.core.backend.ObjectFactory; import io.cucumber.spring.beans.Belly; import io.cucumber.spring.beans.GlueScopedComponent; import io.cucumber.spring.beans.BellyBean; -import io.cucumber.spring.commonglue.AutowiresPlatformTransactionManager; import io.cucumber.spring.commonglue.AutowiresThirdStepDef; import io.cucumber.spring.commonglue.OneStepDef; import io.cucumber.spring.commonglue.ThirdStepDef; @@ -20,7 +19,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.springframework.transaction.PlatformTransactionManager; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -53,29 +51,6 @@ public void shouldGiveUsNewStepInstancesForEachScenario() { assertNotSame(o1, o2); } - @Test - public void shouldGiveUsNewInstancesOfGlueScopeClassesForEachScenario() { - final ObjectFactory factory = new SpringFactory(); - factory.addClass(BellyStepdefs.class); - factory.addClass(AutowiresPlatformTransactionManager.class); - - // Scenario 1 - factory.start(); - final PlatformTransactionManager o1 = - factory.getInstance(AutowiresPlatformTransactionManager.class).getTransactionManager(); - factory.stop(); - - // Scenario 2 - factory.start(); - final PlatformTransactionManager o2 = - factory.getInstance(AutowiresPlatformTransactionManager.class).getTransactionManager(); - factory.stop(); - - assertNotNull(o1); - assertNotNull(o2); - assertNotSame(o1, o2); - } - @Test public void shouldNeverCreateNewApplicationBeanInstances() { // Feature 1 @@ -232,14 +207,12 @@ public void shouldRespectCustomPropertyPlaceholderConfigurer() { @Test public void shouldUseCucumberXmlIfNoClassWithSpringAnnotationIsFound() { final ObjectFactory factory = new SpringFactory(); - factory.addClass(AutowiresPlatformTransactionManager.class); + factory.addClass(Object.class); factory.start(); - final AutowiresPlatformTransactionManager o1 = - factory.getInstance(AutowiresPlatformTransactionManager.class); + final BellyBean o1 = factory.getInstance(BellyBean.class); factory.stop(); assertNotNull(o1); - assertNotNull(o1.getTransactionManager()); } @Test diff --git a/spring/src/test/java/io/cucumber/spring/beans/PlatformTransactionManagerImpl.java b/spring/src/test/java/io/cucumber/spring/beans/PlatformTransactionManagerImpl.java deleted file mode 100644 index 2ca3c71f62..0000000000 --- a/spring/src/test/java/io/cucumber/spring/beans/PlatformTransactionManagerImpl.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.cucumber.spring.beans; - -import org.springframework.transaction.support.TransactionSynchronizationManager; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionDefinition; -import org.springframework.transaction.TransactionException; -import org.springframework.transaction.TransactionStatus; - -public class PlatformTransactionManagerImpl implements PlatformTransactionManager { - - @SuppressWarnings("serial") - @Override - public void commit(TransactionStatus arg0) throws TransactionException { - throw new TransactionException("commit should not be called") { - }; - } - - @Override - public TransactionStatus getTransaction(TransactionDefinition arg0) throws TransactionException { - TransactionSynchronizationManager.setActualTransactionActive(true); - return null; - } - - @Override - public void rollback(TransactionStatus arg0) throws TransactionException { - TransactionSynchronizationManager.setActualTransactionActive(false); - } - -} diff --git a/spring/src/test/java/io/cucumber/spring/commonglue/AnotherStepDef.java b/spring/src/test/java/io/cucumber/spring/commonglue/AnotherStepDef.java index c4c8e11464..cef6140e19 100644 --- a/spring/src/test/java/io/cucumber/spring/commonglue/AnotherStepDef.java +++ b/spring/src/test/java/io/cucumber/spring/commonglue/AnotherStepDef.java @@ -1,6 +1,6 @@ package io.cucumber.spring.commonglue; -import cucumber.api.java.en.Then; +import io.cucumber.java.en.Then; import org.springframework.beans.factory.annotation.Autowired; import static org.junit.Assert.assertEquals; diff --git a/spring/src/test/java/io/cucumber/spring/commonglue/AutowiresPlatformTransactionManager.java b/spring/src/test/java/io/cucumber/spring/commonglue/AutowiresPlatformTransactionManager.java deleted file mode 100644 index 9cebda154e..0000000000 --- a/spring/src/test/java/io/cucumber/spring/commonglue/AutowiresPlatformTransactionManager.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.cucumber.spring.commonglue; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.transaction.PlatformTransactionManager; - -public class AutowiresPlatformTransactionManager { - - @Autowired - private PlatformTransactionManager transactionManager; - - public PlatformTransactionManager getTransactionManager() { - return transactionManager; - } - -} diff --git a/spring/src/test/java/io/cucumber/spring/commonglue/OneStepDef.java b/spring/src/test/java/io/cucumber/spring/commonglue/OneStepDef.java index 9615dbc58a..42fdbe07b2 100644 --- a/spring/src/test/java/io/cucumber/spring/commonglue/OneStepDef.java +++ b/spring/src/test/java/io/cucumber/spring/commonglue/OneStepDef.java @@ -2,8 +2,8 @@ import org.springframework.beans.factory.annotation.Autowired; -import cucumber.api.java.en.Given; -import cucumber.api.java.en.When; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.When; public class OneStepDef { int cucumbers; diff --git a/spring/src/test/java/io/cucumber/spring/commonglue/ThirdStepDef.java b/spring/src/test/java/io/cucumber/spring/commonglue/ThirdStepDef.java index da60dd40a5..2decab06dc 100644 --- a/spring/src/test/java/io/cucumber/spring/commonglue/ThirdStepDef.java +++ b/spring/src/test/java/io/cucumber/spring/commonglue/ThirdStepDef.java @@ -1,6 +1,6 @@ package io.cucumber.spring.commonglue; -import cucumber.api.java.en.Then; +import io.cucumber.java.en.Then; import static org.junit.Assert.assertEquals; diff --git a/spring/src/test/java/io/cucumber/spring/commonglue/TransactionStepDefs.java b/spring/src/test/java/io/cucumber/spring/commonglue/TransactionStepDefs.java deleted file mode 100644 index 1d93026f74..0000000000 --- a/spring/src/test/java/io/cucumber/spring/commonglue/TransactionStepDefs.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.cucumber.spring.commonglue; - -import cucumber.api.java.After; -import cucumber.api.java.Before; -import cucumber.api.java.en.Given; -import cucumber.api.java.en.Then; -import org.springframework.transaction.support.TransactionSynchronizationManager; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -public class TransactionStepDefs { - - @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") - public void the_scenarios_shall_execute_within_a_transaction() throws Throwable { - assertTrue("No transaction is active", - TransactionSynchronizationManager.isActualTransactionActive()); - } - - @Before(value = "@txn", order = 99) - public void before_transaction_scenario() { - assertFalse("A transaction is active", - TransactionSynchronizationManager.isActualTransactionActive()); - } - - @After(value = "@txn", order = 99) - public void after_transaction_scenario() { - assertFalse("A transaction is active", - TransactionSynchronizationManager.isActualTransactionActive()); - } -} diff --git a/spring/src/test/java/io/cucumber/spring/commonglue/UnusedGlue.java b/spring/src/test/java/io/cucumber/spring/commonglue/UnusedGlue.java index 464fab2877..b92237cbd7 100644 --- a/spring/src/test/java/io/cucumber/spring/commonglue/UnusedGlue.java +++ b/spring/src/test/java/io/cucumber/spring/commonglue/UnusedGlue.java @@ -1,7 +1,7 @@ package io.cucumber.spring.commonglue; -import cucumber.api.java.Before; -import cucumber.api.java.en.Given; +import io.cucumber.java.Before; +import io.cucumber.java.en.Given; public class UnusedGlue { public UnusedGlue() { diff --git a/spring/src/test/java/io/cucumber/spring/contextconfig/BellyStepdefs.java b/spring/src/test/java/io/cucumber/spring/contextconfig/BellyStepdefs.java index 232bce423d..5837bad6ff 100644 --- a/spring/src/test/java/io/cucumber/spring/contextconfig/BellyStepdefs.java +++ b/spring/src/test/java/io/cucumber/spring/contextconfig/BellyStepdefs.java @@ -1,6 +1,6 @@ package io.cucumber.spring.contextconfig; -import cucumber.api.java.en.Then; +import io.cucumber.java.en.Then; import io.cucumber.spring.beans.Belly; import io.cucumber.spring.beans.BellyBean; import org.springframework.beans.factory.annotation.Autowired; diff --git a/spring/src/test/java/io/cucumber/spring/contextconfig/RunCucumberTest.java b/spring/src/test/java/io/cucumber/spring/contextconfig/RunCucumberTest.java index 239ec004ca..1f0bff145d 100644 --- a/spring/src/test/java/io/cucumber/spring/contextconfig/RunCucumberTest.java +++ b/spring/src/test/java/io/cucumber/spring/contextconfig/RunCucumberTest.java @@ -9,7 +9,7 @@ glue = { "io.cucumber.spring.contextconfig", "io.cucumber.spring.commonglue", - "cucumber.api.spring" + "io.cucumber.spring.api" }, features = { "classpath:io/cucumber/spring/stepdefInjection.feature", diff --git a/spring/src/test/java/io/cucumber/spring/dirtiescontextconfig/DirtiesContextBellyStepDefs.java b/spring/src/test/java/io/cucumber/spring/dirtiescontextconfig/DirtiesContextBellyStepDefs.java index bd492d0fed..64ce604401 100644 --- a/spring/src/test/java/io/cucumber/spring/dirtiescontextconfig/DirtiesContextBellyStepDefs.java +++ b/spring/src/test/java/io/cucumber/spring/dirtiescontextconfig/DirtiesContextBellyStepDefs.java @@ -1,7 +1,7 @@ package io.cucumber.spring.dirtiescontextconfig; -import cucumber.api.java.en.Given; -import cucumber.api.java.en.Then; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; import io.cucumber.spring.beans.Belly; import io.cucumber.spring.beans.BellyBean; import org.springframework.beans.factory.annotation.Autowired; diff --git a/spring/src/test/java/io/cucumber/spring/hooks/SpringTransactionHooksTest.java b/spring/src/test/java/io/cucumber/spring/hooks/SpringTransactionHooksTest.java deleted file mode 100644 index 4d5f50b078..0000000000 --- a/spring/src/test/java/io/cucumber/spring/hooks/SpringTransactionHooksTest.java +++ /dev/null @@ -1,90 +0,0 @@ -package io.cucumber.spring.hooks; - -import cucumber.api.spring.SpringTransactionHooks; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionDefinition; -import org.springframework.transaction.support.SimpleTransactionStatus; - -import static org.junit.Assert.assertSame; -import static org.mockito.Mockito.isA; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.mockito.quality.Strictness.STRICT_STUBS; - -public class SpringTransactionHooksTest { - - @Rule - public MockitoRule mockitoRule = MockitoJUnit.rule().strictness(STRICT_STUBS); - - private SpringTransactionHooks target; - - @Mock - private BeanFactory mockedBeanFactory; - @Mock - private PlatformTransactionManager mockedPlatformTransactionManager; - - @Before - public void setUp() { - target = new SpringTransactionHooks() { - @Override - public PlatformTransactionManager obtainPlatformTransactionManager() { - return mockedPlatformTransactionManager; - } - }; - target.setBeanFactory(mockedBeanFactory); - } - - @Test - public void shouldObtainPlatformTransactionManagerByTypeWhenTxnManagerBeanNameNotSet() { - SpringTransactionHooks localTarget = new SpringTransactionHooks(); - localTarget.setBeanFactory(mockedBeanFactory); - - when(mockedBeanFactory.getBean(PlatformTransactionManager.class)).thenReturn(mockedPlatformTransactionManager); - - assertSame(localTarget.obtainPlatformTransactionManager(), mockedPlatformTransactionManager); - - verify(mockedBeanFactory).getBean(PlatformTransactionManager.class); - } - - @Test - public void shouldObtainPlatformTransactionManagerByNameWhenTxnManagerBeanNameIsSet() { - SpringTransactionHooks localTarget = new SpringTransactionHooks(); - localTarget.setBeanFactory(mockedBeanFactory); - final String txnManagerBeanName = "myTxnManagerBeanName"; - localTarget.setTxnManagerBeanName(txnManagerBeanName); - - when(mockedBeanFactory.getBean(txnManagerBeanName, PlatformTransactionManager.class)).thenReturn(mockedPlatformTransactionManager); - - assertSame(localTarget.obtainPlatformTransactionManager(), mockedPlatformTransactionManager); - - verify(mockedBeanFactory).getBean(txnManagerBeanName, PlatformTransactionManager.class); - } - - @Test - public void shouldObtainOrStartTransactionInBeforeHook() { - final SimpleTransactionStatus dummyTxStatus = new SimpleTransactionStatus(); - when(mockedPlatformTransactionManager.getTransaction(isA(TransactionDefinition.class))).thenReturn(dummyTxStatus); - - target.startTransaction(); - - assertSame(target.getTransactionStatus(), dummyTxStatus); - } - - @Test - public void shouldTriggerTransactionRollbackInAfterHook() { - final SimpleTransactionStatus dummyTxStatus = new SimpleTransactionStatus(); - target.setTransactionStatus(dummyTxStatus); - - target.rollBackTransaction(); - - verify(mockedPlatformTransactionManager).rollback(dummyTxStatus); - } - -} \ No newline at end of file diff --git a/spring/src/test/java/io/cucumber/spring/metaconfig/dirties/DirtiesContextBellyMetaStepDefs.java b/spring/src/test/java/io/cucumber/spring/metaconfig/dirties/DirtiesContextBellyMetaStepDefs.java index 22ad364a39..5ff3bcefe8 100644 --- a/spring/src/test/java/io/cucumber/spring/metaconfig/dirties/DirtiesContextBellyMetaStepDefs.java +++ b/spring/src/test/java/io/cucumber/spring/metaconfig/dirties/DirtiesContextBellyMetaStepDefs.java @@ -1,7 +1,7 @@ package io.cucumber.spring.metaconfig.dirties; -import cucumber.api.java.en.Given; -import cucumber.api.java.en.Then; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; import io.cucumber.spring.beans.Belly; import io.cucumber.spring.beans.BellyBean; import org.springframework.beans.factory.annotation.Autowired; diff --git a/spring/src/test/java/io/cucumber/spring/metaconfig/general/BellyMetaStepdefs.java b/spring/src/test/java/io/cucumber/spring/metaconfig/general/BellyMetaStepdefs.java index a613865994..11d2d9fe8b 100644 --- a/spring/src/test/java/io/cucumber/spring/metaconfig/general/BellyMetaStepdefs.java +++ b/spring/src/test/java/io/cucumber/spring/metaconfig/general/BellyMetaStepdefs.java @@ -1,6 +1,6 @@ package io.cucumber.spring.metaconfig.general; -import cucumber.api.java.en.Then; +import io.cucumber.java.en.Then; import io.cucumber.spring.beans.Belly; import io.cucumber.spring.beans.BellyBean; import org.springframework.beans.factory.annotation.Autowired; diff --git a/spring/src/test/java/io/cucumber/spring/threading/RunParallelCukesTest.java b/spring/src/test/java/io/cucumber/spring/threading/RunParallelCukesTest.java index b51dac867f..650e4dab67 100644 --- a/spring/src/test/java/io/cucumber/spring/threading/RunParallelCukesTest.java +++ b/spring/src/test/java/io/cucumber/spring/threading/RunParallelCukesTest.java @@ -3,7 +3,7 @@ import static java.util.concurrent.Executors.newFixedThreadPool; import static org.junit.Assert.assertEquals; -import cucumber.api.cli.Main; +import io.cucumber.core.cli.Main; import org.junit.Test; import java.util.concurrent.Callable; diff --git a/spring/src/test/java/io/cucumber/spring/threading/ThreadingStepDefs.java b/spring/src/test/java/io/cucumber/spring/threading/ThreadingStepDefs.java index 0680343519..c7c8397f11 100644 --- a/spring/src/test/java/io/cucumber/spring/threading/ThreadingStepDefs.java +++ b/spring/src/test/java/io/cucumber/spring/threading/ThreadingStepDefs.java @@ -5,9 +5,9 @@ import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; -import cucumber.api.java.en.Given; -import cucumber.api.java.en.Then; -import cucumber.api.java.en.When; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.web.WebAppConfiguration; diff --git a/spring/src/test/java/io/cucumber/spring/webappconfig/SpringInjectionStepDefs.java b/spring/src/test/java/io/cucumber/spring/webappconfig/SpringInjectionStepDefs.java index 85d99ee084..8863b020bb 100644 --- a/spring/src/test/java/io/cucumber/spring/webappconfig/SpringInjectionStepDefs.java +++ b/spring/src/test/java/io/cucumber/spring/webappconfig/SpringInjectionStepDefs.java @@ -1,8 +1,8 @@ package io.cucumber.spring.webappconfig; -import cucumber.api.java.en.Given; -import cucumber.api.java.en.Then; -import cucumber.api.java.en.When; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.web.WebAppConfiguration; diff --git a/spring/src/test/resources/applicationContext.xml b/spring/src/test/resources/applicationContext.xml index 4d40134113..43cdf9b61d 100644 --- a/spring/src/test/resources/applicationContext.xml +++ b/spring/src/test/resources/applicationContext.xml @@ -14,7 +14,4 @@ - - - diff --git a/testng/README.md b/testng/README.md index e4ff4ef645..00c5005af6 100644 --- a/testng/README.md +++ b/testng/README.md @@ -1,9 +1,7 @@ Cucumber TestNG ============== -Use TestNG to execute cucumber scenarios. - -Add the `cucumber-testng` dependency to your pom. +Use TestNG to execute cucumber scenarios. To use add the `cucumber-testng` dependency to your pom. ```xml diff --git a/testng/pom.xml b/testng/pom.xml index 56423e2aca..7c7fe80479 100644 --- a/testng/pom.xml +++ b/testng/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 4.7.2-SNAPSHOT + 5.0.0-SNAPSHOT cucumber-testng diff --git a/testng/src/main/java/cucumber/api/testng/AbstractTestNGCucumberTests.java b/testng/src/main/java/cucumber/api/testng/AbstractTestNGCucumberTests.java deleted file mode 100644 index 78587e3fdf..0000000000 --- a/testng/src/main/java/cucumber/api/testng/AbstractTestNGCucumberTests.java +++ /dev/null @@ -1,50 +0,0 @@ -package cucumber.api.testng; - -import org.apiguardian.api.API; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -/** - * Runs each cucumber scenario found in the features as separated test - * - * @deprecated use {@link io.cucumber.testng.AbstractTestNGCucumberTests} instead. - */ -@Deprecated -@API(status = API.Status.MAINTAINED) -public abstract class AbstractTestNGCucumberTests { - private TestNGCucumberRunner testNGCucumberRunner; - - @BeforeClass(alwaysRun = true) - public void setUpClass() throws Exception { - testNGCucumberRunner = new TestNGCucumberRunner(this.getClass()); - } - - @Test(groups = "cucumber", description = "Runs Cucumber Scenarios", dataProvider = "scenarios") - public void runScenario(PickleEventWrapper pickleWrapper, CucumberFeatureWrapper featureWrapper) throws Throwable { - // the 'featureWrapper' parameter solely exists to display the feature file in a test report - testNGCucumberRunner.runScenario(pickleWrapper.getPickleEvent()); - } - - /** - * Returns two dimensional array of PickleEventWrapper scenarios with their associated CucumberFeatureWrapper feature. - * - * @return a two dimensional array of scenarios features. - */ - @DataProvider - public Object[][] scenarios() { - if (testNGCucumberRunner == null) { - return new Object[0][0]; - } - return testNGCucumberRunner.provideScenarios(); - } - - @AfterClass(alwaysRun = true) - public void tearDownClass() throws Exception { - if (testNGCucumberRunner == null) { - return; - } - testNGCucumberRunner.finish(); - } -} diff --git a/testng/src/main/java/cucumber/api/testng/CucumberFeatureWrapper.java b/testng/src/main/java/cucumber/api/testng/CucumberFeatureWrapper.java deleted file mode 100644 index ef7a841910..0000000000 --- a/testng/src/main/java/cucumber/api/testng/CucumberFeatureWrapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package cucumber.api.testng; - -import org.apiguardian.api.API; - -/** - * The only purpose of this interface is to be able to provide a custom - *
toString()
, making TestNG reports look more descriptive. - * - * @see AbstractTestNGCucumberTests#runScenario(cucumber.api.testng.PickleEventWrapper, cucumber.api.testng.CucumberFeatureWrapper) - * @deprecated use {@link io.cucumber.testng.CucumberFeatureWrapper} instead. - */ -@Deprecated -@API(status = API.Status.MAINTAINED) -public interface CucumberFeatureWrapper extends io.cucumber.testng.CucumberFeatureWrapper { - -} diff --git a/testng/src/main/java/cucumber/api/testng/CucumberFeatureWrapperImpl.java b/testng/src/main/java/cucumber/api/testng/CucumberFeatureWrapperImpl.java deleted file mode 100644 index 479f6afb07..0000000000 --- a/testng/src/main/java/cucumber/api/testng/CucumberFeatureWrapperImpl.java +++ /dev/null @@ -1,18 +0,0 @@ -package cucumber.api.testng; - -/** - * @deprecated use {@link io.cucumber.testng.CucumberFeatureWrapper} instead - */ -@Deprecated -class CucumberFeatureWrapperImpl implements CucumberFeatureWrapper { - private final io.cucumber.testng.CucumberFeatureWrapper delegate; - - CucumberFeatureWrapperImpl(io.cucumber.testng.CucumberFeatureWrapper cucumberFeature) { - this.delegate = cucumberFeature; - } - - @Override - public String toString() { - return delegate.toString(); - } -} diff --git a/testng/src/main/java/cucumber/api/testng/PickleEventWrapper.java b/testng/src/main/java/cucumber/api/testng/PickleEventWrapper.java deleted file mode 100644 index a5c228baa0..0000000000 --- a/testng/src/main/java/cucumber/api/testng/PickleEventWrapper.java +++ /dev/null @@ -1,19 +0,0 @@ -package cucumber.api.testng; - -import gherkin.events.PickleEvent; -import org.apiguardian.api.API; - -/** - * The only purpose of this interface is to be able to provide a custom - *
toString()
, making TestNG reports look more descriptive. - * - * @see AbstractTestNGCucumberTests#runScenario(cucumber.api.testng.PickleEventWrapper, cucumber.api.testng.CucumberFeatureWrapper) - * @deprecated use {@link io.cucumber.testng.CucumberFeatureWrapper} instead. - */ -@Deprecated -@API(status = API.Status.MAINTAINED) -public interface PickleEventWrapper extends io.cucumber.testng.PickleEventWrapper { - - PickleEvent getPickleEvent(); - -} diff --git a/testng/src/main/java/cucumber/api/testng/PickleEventWrapperImpl.java b/testng/src/main/java/cucumber/api/testng/PickleEventWrapperImpl.java deleted file mode 100644 index 4eca2be6bc..0000000000 --- a/testng/src/main/java/cucumber/api/testng/PickleEventWrapperImpl.java +++ /dev/null @@ -1,25 +0,0 @@ -package cucumber.api.testng; - -import gherkin.events.PickleEvent; - -/** - * @deprecated use {@link io.cucumber.testng.PickleEventWrapper} instead - */ -@Deprecated -class PickleEventWrapperImpl implements PickleEventWrapper { - - private final io.cucumber.testng.PickleEventWrapper delegate; - - PickleEventWrapperImpl(io.cucumber.testng.PickleEventWrapper delegate) { - this.delegate = delegate; - } - - public PickleEvent getPickleEvent() { - return delegate.getPickleEvent(); - } - - @Override - public String toString() { - return delegate.toString(); - } -} diff --git a/testng/src/main/java/cucumber/api/testng/TestNGCucumberRunner.java b/testng/src/main/java/cucumber/api/testng/TestNGCucumberRunner.java deleted file mode 100644 index d02b69af72..0000000000 --- a/testng/src/main/java/cucumber/api/testng/TestNGCucumberRunner.java +++ /dev/null @@ -1,55 +0,0 @@ -package cucumber.api.testng; - -import gherkin.events.PickleEvent; -import io.cucumber.testng.CucumberFeatureWrapper; -import io.cucumber.testng.PickleEventWrapper; -import org.apiguardian.api.API; - -/** - * Glue code for running Cucumber via TestNG. - * - * @deprecated use {@code io.cucumber.testng.TestNGCucumberRunner} instead - */ -@API(status = API.Status.MAINTAINED) -@Deprecated -public class TestNGCucumberRunner { - - private final io.cucumber.testng.TestNGCucumberRunner delegate; - - public TestNGCucumberRunner(Class clazz) { - this.delegate = new io.cucumber.testng.TestNGCucumberRunner(clazz); - } - - public void runScenario(PickleEvent pickle) throws Throwable { - delegate.runScenario(pickle); - } - - public void finish() { - delegate.finish(); - } - - public Object[][] provideScenarios() { - return addLegacyWrappers(delegate.provideScenarios()); - } - - private Object[][] addLegacyWrappers(Object[][] objects) { - for (Object[] row : objects) { - for (int i = 0; i < row.length; i++) { - Object element = row[i]; - if (element == null) { - continue; - } - - if (element instanceof PickleEventWrapper) { - PickleEventWrapper wrapper = (PickleEventWrapper) element; - row[i] = new PickleEventWrapperImpl(wrapper); - } else if (element instanceof CucumberFeatureWrapper) { - CucumberFeatureWrapper wrapper = (CucumberFeatureWrapper) element; - row[i] = new CucumberFeatureWrapperImpl(wrapper); - } - } - } - - return objects; - } -} \ No newline at end of file diff --git a/testng/src/main/java/io/cucumber/testng/AbstractTestNGCucumberTests.java b/testng/src/main/java/io/cucumber/testng/AbstractTestNGCucumberTests.java index 6428ef885e..73853567da 100644 --- a/testng/src/main/java/io/cucumber/testng/AbstractTestNGCucumberTests.java +++ b/testng/src/main/java/io/cucumber/testng/AbstractTestNGCucumberTests.java @@ -7,7 +7,11 @@ import org.testng.annotations.Test; /** - * Runs each cucumber scenario found in the features as separated test + * Abstract TestNG Cucumber Test + *

+ * Runs each cucumber scenario found in the features as separated test. + * + * @see TestNGCucumberRunner */ @API(status = API.Status.STABLE) public abstract class AbstractTestNGCucumberTests { diff --git a/testng/src/main/java/io/cucumber/testng/CucumberExceptionWrapper.java b/testng/src/main/java/io/cucumber/testng/CucumberExceptionWrapper.java index a7e8cc64e9..f98d09484f 100644 --- a/testng/src/main/java/io/cucumber/testng/CucumberExceptionWrapper.java +++ b/testng/src/main/java/io/cucumber/testng/CucumberExceptionWrapper.java @@ -1,6 +1,6 @@ package io.cucumber.testng; -import cucumber.runtime.CucumberException; +import io.cucumber.core.exception.CucumberException; import gherkin.events.PickleEvent; /** diff --git a/testng/src/main/java/io/cucumber/testng/CucumberFeatureWrapperImpl.java b/testng/src/main/java/io/cucumber/testng/CucumberFeatureWrapperImpl.java index 31f7e06507..3d930108a7 100644 --- a/testng/src/main/java/io/cucumber/testng/CucumberFeatureWrapperImpl.java +++ b/testng/src/main/java/io/cucumber/testng/CucumberFeatureWrapperImpl.java @@ -1,6 +1,6 @@ package io.cucumber.testng; -import cucumber.runtime.model.CucumberFeature; +import io.cucumber.core.feature.CucumberFeature; class CucumberFeatureWrapperImpl implements CucumberFeatureWrapper { private final CucumberFeature cucumberFeature; diff --git a/testng/src/main/java/io/cucumber/testng/CucumberOptions.java b/testng/src/main/java/io/cucumber/testng/CucumberOptions.java index b9006f82e5..9ebe07a08d 100644 --- a/testng/src/main/java/io/cucumber/testng/CucumberOptions.java +++ b/testng/src/main/java/io/cucumber/testng/CucumberOptions.java @@ -1,6 +1,5 @@ package io.cucumber.testng; -import cucumber.api.Plugin; import org.apiguardian.api.API; import java.lang.annotation.ElementType; @@ -34,7 +33,7 @@ * class. For example, if the annotated class is {@code com.example.RunCucumber} * then features are assumed to be located in {@code classpath:com/example}. * - * @see io.cucumber.core.model.FeatureWithLines + * @see io.cucumber.core.feature.FeatureWithLines */ String[] features() default {}; @@ -46,7 +45,7 @@ * class. For example, if the annotated class is {@code com.example.RunCucumber} * then glue is assumed to be located in {@code com.example}. * - * @see io.cucumber.core.model.GluePath + * @see io.cucumber.core.feature.GluePath */ String[] glue() default {}; @@ -77,7 +76,7 @@ * Plugins can be provided with an argument. For example * {@code json:target/cucumber-report.json} * - * @see Plugin + * @see io.cucumber.core.plugin.Plugin */ String[] plugin() default {}; @@ -96,6 +95,16 @@ */ SnippetType snippets() default SnippetType.UNDERSCORE; + /** + * Specify a custom ObjectFactory. + *

+ * In case a custom ObjectFactory is needed, the class can be specified here. + * A custom ObjectFactory might be needed when more granular control is needed + * over the dependency injection mechanism. + */ + Class objectFactory() default NoObjectFactory.class; + + enum SnippetType { UNDERSCORE, CAMELCASE } diff --git a/testng/src/main/java/io/cucumber/testng/NoObjectFactory.java b/testng/src/main/java/io/cucumber/testng/NoObjectFactory.java new file mode 100644 index 0000000000..5dbea2ddfb --- /dev/null +++ b/testng/src/main/java/io/cucumber/testng/NoObjectFactory.java @@ -0,0 +1,30 @@ +package io.cucumber.testng; + +import io.cucumber.core.backend.ObjectFactory; + +/** + * This object factory does nothing. It is solely needed for marking purposes. + */ +final class NoObjectFactory implements ObjectFactory { + + private NoObjectFactory() { + // No need for instantiation + } + + @Override + public boolean addClass(Class glueClass) { + return false; + } + + @Override + public T getInstance(Class glueClass) { + return null; + } + + @Override + public void start() {} + + @Override + public void stop() {} + +} diff --git a/testng/src/main/java/io/cucumber/testng/TestCaseResultListener.java b/testng/src/main/java/io/cucumber/testng/TestCaseResultListener.java index 2bbfcfcb00..d650bb0e6f 100644 --- a/testng/src/main/java/io/cucumber/testng/TestCaseResultListener.java +++ b/testng/src/main/java/io/cucumber/testng/TestCaseResultListener.java @@ -1,10 +1,11 @@ package io.cucumber.testng; -import cucumber.api.Result; -import cucumber.api.event.EventHandler; -import cucumber.api.event.TestCaseFinished; -import cucumber.runner.EventBus; -import cucumber.runtime.CucumberException; +import io.cucumber.core.event.EventHandler; +import io.cucumber.core.event.Result; +import io.cucumber.core.event.Status; +import io.cucumber.core.event.TestCaseFinished; +import io.cucumber.core.eventbus.EventBus; +import io.cucumber.core.exception.CucumberException; import org.testng.SkipException; class TestCaseResultListener { @@ -16,7 +17,7 @@ class TestCaseResultListener { private final EventHandler testCaseFinishedHandler = new EventHandler() { @Override public void receive(TestCaseFinished event) { - receiveResult(event.result); + receiveResult(event.getResult()); } }; @@ -36,7 +37,7 @@ void receiveResult(Result result) { } boolean isPassed() { - return result == null || result.is(Result.Type.PASSED); + return result == null || result.getStatus().is(Status.PASSED); } Throwable getError() { @@ -51,7 +52,7 @@ Throwable getError() { if (strict) { return result.getError(); } else { - return new SkipException(result.getErrorMessage(), result.getError()); + return new SkipException(result.getError().getMessage(), result.getError()); } case UNDEFINED: if (strict) { @@ -65,7 +66,7 @@ Throwable getError() { if (error instanceof SkipException) { return error; } else { - return new SkipException(result.getErrorMessage(), error); + return new SkipException(result.getError().getMessage(), error); } } else { return new SkipException(SKIPPED_MESSAGE); diff --git a/testng/src/main/java/io/cucumber/testng/TestNGCucumberOptionsProvider.java b/testng/src/main/java/io/cucumber/testng/TestNGCucumberOptionsProvider.java index ceec60abf8..45cc41595a 100644 --- a/testng/src/main/java/io/cucumber/testng/TestNGCucumberOptionsProvider.java +++ b/testng/src/main/java/io/cucumber/testng/TestNGCucumberOptionsProvider.java @@ -1,6 +1,7 @@ package io.cucumber.testng; -import cucumber.api.SnippetType; +import io.cucumber.core.snippets.SnippetType; +import io.cucumber.core.backend.ObjectFactory; import io.cucumber.core.options.CucumberOptionsAnnotationParser; class TestNGCucumberOptionsProvider implements CucumberOptionsAnnotationParser.OptionsProvider { @@ -10,13 +11,13 @@ public CucumberOptionsAnnotationParser.CucumberOptions getOptions(Class clazz if (annotation == null) { return null; } - return new JunitCucumberOptions(annotation); + return new TestNGCucumberOptions(annotation); } - private static class JunitCucumberOptions implements CucumberOptionsAnnotationParser.CucumberOptions { + private static class TestNGCucumberOptions implements CucumberOptionsAnnotationParser.CucumberOptions { private final CucumberOptions annotation; - JunitCucumberOptions(CucumberOptions annotation) { + TestNGCucumberOptions(CucumberOptions annotation) { this.annotation = annotation; } @@ -68,18 +69,18 @@ public String[] name() { @Override public SnippetType snippets() { switch (annotation.snippets()) { - case UNDERSCORE: - return SnippetType.UNDERSCORE; - case CAMELCASE: - return SnippetType.CAMELCASE; - default: - throw new IllegalArgumentException("" + annotation.snippets()); + case UNDERSCORE: + return SnippetType.UNDERSCORE; + case CAMELCASE: + return SnippetType.CAMELCASE; + default: + throw new IllegalArgumentException("" + annotation.snippets()); } } @Override - public String[] junit() { - return new String[0]; + public Class objectFactory() { + return (annotation.objectFactory() == NoObjectFactory.class) ? null : annotation.objectFactory(); } } } diff --git a/testng/src/main/java/io/cucumber/testng/TestNGCucumberRunner.java b/testng/src/main/java/io/cucumber/testng/TestNGCucumberRunner.java index c5771b958a..4fc0206404 100644 --- a/testng/src/main/java/io/cucumber/testng/TestNGCucumberRunner.java +++ b/testng/src/main/java/io/cucumber/testng/TestNGCucumberRunner.java @@ -1,37 +1,60 @@ package io.cucumber.testng; -import cucumber.api.event.TestRunFinished; -import cucumber.api.event.TestRunStarted; -import cucumber.runner.*; -import cucumber.runtime.*; -import cucumber.runtime.filter.Filters; -import cucumber.runtime.formatter.PluginFactory; -import cucumber.runtime.formatter.Plugins; -import cucumber.runtime.io.MultiLoader; -import cucumber.runtime.io.ResourceLoader; -import cucumber.runtime.io.ResourceLoaderClassFinder; -import cucumber.runtime.model.CucumberFeature; -import cucumber.runtime.model.FeatureLoader; import gherkin.events.PickleEvent; -import io.cucumber.core.options.EnvironmentOptionsParser; -import io.cucumber.core.options.RuntimeOptions; +import io.cucumber.core.backend.ObjectFactoryServiceLoader; +import io.cucumber.core.event.TestRunFinished; +import io.cucumber.core.event.TestRunStarted; +import io.cucumber.core.event.TestSourceRead; +import io.cucumber.core.eventbus.EventBus; +import io.cucumber.core.exception.CucumberException; +import io.cucumber.core.feature.CucumberFeature; +import io.cucumber.core.feature.FeatureLoader; +import io.cucumber.core.filter.Filters; +import io.cucumber.core.io.ClassFinder; +import io.cucumber.core.io.MultiLoader; +import io.cucumber.core.io.ResourceLoader; +import io.cucumber.core.io.ResourceLoaderClassFinder; +import io.cucumber.core.options.Constants; import io.cucumber.core.options.CucumberOptionsAnnotationParser; +import io.cucumber.core.options.CucumberProperties; +import io.cucumber.core.options.CucumberPropertiesParser; +import io.cucumber.core.options.RuntimeOptions; +import io.cucumber.core.plugin.PluginFactory; +import io.cucumber.core.plugin.Plugins; +import io.cucumber.core.runner.Runner; +import io.cucumber.core.runtime.BackendServiceLoader; +import io.cucumber.core.runtime.ScanningTypeRegistryConfigurerSupplier; +import io.cucumber.core.runtime.FeaturePathFeatureSupplier; +import io.cucumber.core.runtime.ObjectFactorySupplier; +import io.cucumber.core.runtime.ThreadLocalObjectFactorySupplier; +import io.cucumber.core.runtime.ThreadLocalRunnerSupplier; +import io.cucumber.core.runtime.TimeServiceEventBus; +import io.cucumber.core.runtime.TypeRegistryConfigurerSupplier; import org.apiguardian.api.API; +import java.time.Clock; import java.util.ArrayList; import java.util.List; /** * Glue code for running Cucumber via TestNG. + *

+ * Options can be provided in order of precedence by: + *

    + *
  1. Setting {@value Constants#CUCUMBER_OPTIONS_PROPERTY_NAME} property in {@link System#getProperties()} ()}
  2. + *
  3. Setting {@value Constants#CUCUMBER_OPTIONS_PROPERTY_NAME} property in {@link System#getenv()}
  4. + *
  5. Annotating the runner class with {@link CucumberOptions}
  6. + *
  7. Setting {@value Constants#CUCUMBER_OPTIONS_PROPERTY_NAME} property in {@value Constants#CUCUMBER_PROPERTIES_FILE_NAME}
  8. + *
*/ @API(status = API.Status.STABLE) public final class TestNGCucumberRunner { private final EventBus bus; private final Filters filters; - private final FeaturePathFeatureSupplier featureSupplier; private final ThreadLocalRunnerSupplier runnerSupplier; private final RuntimeOptions runtimeOptions; private final Plugins plugins; + private final FeaturePathFeatureSupplier featureSupplier; /** * Bootstrap the cucumber runtime @@ -40,26 +63,40 @@ public final class TestNGCucumberRunner { * and {@link org.testng.annotations.Test} annotations */ public TestNGCucumberRunner(Class clazz) { + ClassLoader classLoader = clazz.getClassLoader(); ResourceLoader resourceLoader = new MultiLoader(classLoader); + ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader); // Parse the options early to provide fast feedback about invalid options + RuntimeOptions propertiesFileOptions = new CucumberPropertiesParser(resourceLoader) + .parse(CucumberProperties.fromPropertiesFile()) + .build(); + RuntimeOptions annotationOptions = new CucumberOptionsAnnotationParser(resourceLoader) .withOptionsProvider(new TestNGCucumberOptionsProvider()) .parse(clazz) - .build(); - runtimeOptions = new EnvironmentOptionsParser(resourceLoader) - .parse(Env.INSTANCE) + .build(propertiesFileOptions); + + RuntimeOptions environmentOptions = new CucumberPropertiesParser(resourceLoader) + .parse(CucumberProperties.fromEnvironment()) .build(annotationOptions); - ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader); - BackendModuleBackendSupplier backendSupplier = new BackendModuleBackendSupplier(resourceLoader, classFinder, runtimeOptions); - bus = new TimeServiceEventBus(TimeService.SYSTEM); - plugins = new Plugins(classLoader, new PluginFactory(), runtimeOptions); + runtimeOptions = new CucumberPropertiesParser(resourceLoader) + .parse(CucumberProperties.fromSystemProperties()) + .build(environmentOptions); + FeatureLoader featureLoader = new FeatureLoader(resourceLoader); - filters = new Filters(runtimeOptions); - this.runnerSupplier = new ThreadLocalRunnerSupplier(runtimeOptions, bus, backendSupplier); featureSupplier = new FeaturePathFeatureSupplier(featureLoader, runtimeOptions); + + this.bus = new TimeServiceEventBus(Clock.systemUTC()); + this.plugins = new Plugins(new PluginFactory(), runtimeOptions); + ObjectFactoryServiceLoader objectFactoryServiceLoader = new ObjectFactoryServiceLoader(runtimeOptions); + ObjectFactorySupplier objectFactorySupplier = new ThreadLocalObjectFactorySupplier(objectFactoryServiceLoader); + BackendServiceLoader backendSupplier = new BackendServiceLoader(resourceLoader, objectFactorySupplier); + this.filters = new Filters(runtimeOptions); + TypeRegistryConfigurerSupplier typeRegistryConfigurerSupplier = new ScanningTypeRegistryConfigurerSupplier(classFinder, runtimeOptions); + this.runnerSupplier = new ThreadLocalRunnerSupplier(runtimeOptions, bus, backendSupplier, objectFactorySupplier, typeRegistryConfigurerSupplier); } public void runScenario(PickleEvent pickle) throws Throwable { @@ -75,7 +112,7 @@ public void runScenario(PickleEvent pickle) throws Throwable { } public void finish() { - bus.send(new TestRunFinished(bus.getTime(), bus.getTimeMillis())); + bus.send(new TestRunFinished(bus.getInstant())); } /** @@ -104,9 +141,9 @@ private List getFeatures() { plugins.setSerialEventBusOnEventListenerPlugins(bus); List features = featureSupplier.get(); - bus.send(new TestRunStarted(bus.getTime(), bus.getTimeMillis())); + bus.send(new TestRunStarted(bus.getInstant())); for (CucumberFeature feature : features) { - feature.sendTestSourceRead(bus); + bus.send(new TestSourceRead(bus.getInstant(), feature.getUri().toString(), feature.getSource())); } return features; } diff --git a/testng/src/test/java/cucumber/runtime/stub/StubBackend.java b/testng/src/test/java/cucumber/runtime/stub/StubBackend.java deleted file mode 100644 index eff447888d..0000000000 --- a/testng/src/test/java/cucumber/runtime/stub/StubBackend.java +++ /dev/null @@ -1,40 +0,0 @@ -package cucumber.runtime.stub; - -import io.cucumber.stepexpression.TypeRegistry; -import cucumber.runtime.Backend; -import cucumber.runtime.Glue; -import cucumber.runtime.io.ResourceLoader; -import cucumber.runtime.snippets.FunctionNameGenerator; -import gherkin.pickles.PickleStep; - -import java.net.URI; -import java.util.List; - -import static java.util.Collections.singletonList; - -/** - * We need an implementation of Backend to prevent Runtime from blowing up. - */ -@SuppressWarnings("unused") -public class StubBackend implements Backend { - public StubBackend(ResourceLoader resourceLoader, TypeRegistry typeRegistry) { - - } - - @Override - public void loadGlue(Glue glue, List gluePaths) { - } - - @Override - public void buildWorld() { - } - - @Override - public void disposeWorld() { - } - - @Override - public List getSnippet(PickleStep step, String keyword, FunctionNameGenerator functionNameGenerator) { - return singletonList("STUB SNIPPET"); - } -} diff --git a/testng/src/test/java/io/cucumber/testng/StubBackendProviderService.java b/testng/src/test/java/io/cucumber/testng/StubBackendProviderService.java new file mode 100644 index 0000000000..a8e3996463 --- /dev/null +++ b/testng/src/test/java/io/cucumber/testng/StubBackendProviderService.java @@ -0,0 +1,112 @@ +package io.cucumber.testng; + + +import io.cucumber.core.backend.Backend; +import io.cucumber.core.backend.BackendProviderService; +import io.cucumber.core.backend.Container; +import io.cucumber.core.backend.Glue; +import io.cucumber.core.backend.Lookup; +import io.cucumber.core.backend.ParameterInfo; +import io.cucumber.core.backend.StepDefinition; +import io.cucumber.core.io.ResourceLoader; +import io.cucumber.core.snippets.Snippet; + +import java.lang.reflect.Type; +import java.net.URI; +import java.text.MessageFormat; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class StubBackendProviderService implements BackendProviderService { + + @Override + public Backend create(Lookup lookup, Container container, ResourceLoader resourceLoader) { + return new StubBackend(); + } + + /** + * We need an implementation of Backend to prevent Runtime from blowing up. + */ + private static class StubBackend implements Backend { + StubBackend() { + + } + + @Override + public void loadGlue(Glue glue, List gluePaths) { + glue.addStepDefinition(createStepDefinition("background step")); + glue.addStepDefinition(createStepDefinition("scenario name")); + glue.addStepDefinition(createStepDefinition("scenario C")); + glue.addStepDefinition(createStepDefinition("scenario D")); + glue.addStepDefinition(createStepDefinition("scenario E")); + glue.addStepDefinition(createStepDefinition("first step")); + glue.addStepDefinition(createStepDefinition("second step")); + glue.addStepDefinition(createStepDefinition("third step")); + + } + + private StepDefinition createStepDefinition(final String pattern) { + return new StepDefinition() { + + @Override + public String getLocation(boolean detail) { + return null; + } + + @Override + public void execute(Object[] args) { + + } + + @Override + public boolean isDefinedAt(StackTraceElement stackTraceElement) { + return false; + } + + @Override + public List parameterInfos() { + return Collections.emptyList(); + } + + @Override + public String getPattern() { + return pattern; + } + }; + } + + @Override + public void buildWorld() { + } + + @Override + public void disposeWorld() { + } + + @Override + public Snippet getSnippet() { + return new Snippet() { + @Override + public MessageFormat template() { + return new MessageFormat(""); + } + + @Override + public String tableHint() { + return ""; + } + + @Override + public String arguments(Map arguments) { + return ""; + } + + @Override + public String escapePattern(String pattern) { + return ""; + } + }; + } + } +} diff --git a/testng/src/test/java/io/cucumber/testng/TestCaseResultListenerTest.java b/testng/src/test/java/io/cucumber/testng/TestCaseResultListenerTest.java index d1b2fefba0..d183a957a2 100644 --- a/testng/src/test/java/io/cucumber/testng/TestCaseResultListenerTest.java +++ b/testng/src/test/java/io/cucumber/testng/TestCaseResultListenerTest.java @@ -1,20 +1,23 @@ package io.cucumber.testng; -import cucumber.api.Result; -import cucumber.runner.EventBus; -import cucumber.runner.TimeService; -import cucumber.runner.TimeServiceEventBus; +import io.cucumber.core.event.Result; +import io.cucumber.core.event.Status; +import io.cucumber.core.eventbus.EventBus; +import io.cucumber.core.runtime.TimeServiceEventBus; import org.testng.SkipException; import org.testng.annotations.Test; +import static java.time.Duration.ZERO; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; +import java.time.Clock; + public class TestCaseResultListenerTest { - private final EventBus bus = new TimeServiceEventBus(TimeService.SYSTEM); + private final EventBus bus = new TimeServiceEventBus(Clock.systemUTC()); @Test public void should_be_passed_for_passed_result() { @@ -99,27 +102,27 @@ public void should_be_skipped_for_skipped_result() { } private Result mockPassedResult() { - return new Result(Result.Type.PASSED, 0L, null); + return new Result(Status.PASSED, ZERO, null); } private Result mockSkippedResult() { - return new Result(Result.Type.SKIPPED, 0L, null); + return new Result(Status.SKIPPED, ZERO, null); } private Result mockUndefinedResult() { - return new Result(Result.Type.UNDEFINED, 0L, null); + return new Result(Status.UNDEFINED, ZERO, null); } private Result mockFailedResult() { - return new Result(Result.Type.FAILED, 0L, new Exception()); + return new Result(Status.FAILED, ZERO, new Exception()); } private Result mockAmbiguousResult() { - return new Result(Result.Type.AMBIGUOUS, 0L, new Exception()); + return new Result(Status.AMBIGUOUS, ZERO, new Exception()); } private Result mockPendingResult() { - return new Result(Result.Type.PENDING, 0L, new TestPendingException()); + return new Result(Status.PENDING, ZERO, new TestPendingException()); } } diff --git a/testng/src/test/java/io/cucumber/testng/TestNGCucumberOptionsProviderTest.java b/testng/src/test/java/io/cucumber/testng/TestNGCucumberOptionsProviderTest.java new file mode 100644 index 0000000000..a2d8fdfd31 --- /dev/null +++ b/testng/src/test/java/io/cucumber/testng/TestNGCucumberOptionsProviderTest.java @@ -0,0 +1,56 @@ +package io.cucumber.testng; + + +import static org.testng.Assert.*; + +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import io.cucumber.core.backend.ObjectFactory; + + +final class TestNGCucumberOptionsProviderTest { + + private TestNGCucumberOptionsProvider optionsProvider; + + @BeforeTest + void setUp() throws Exception { + this.optionsProvider = new TestNGCucumberOptionsProvider(); + } + @Test + void testObjectFactoryWhenNotSpecified() { + io.cucumber.core.options.CucumberOptionsAnnotationParser.CucumberOptions options = this.optionsProvider.getOptions(ClassWithDefault.class); + assertNull(options.objectFactory()); + } + + @Test + void testObjectFactory() { + io.cucumber.core.options.CucumberOptionsAnnotationParser.CucumberOptions options = this.optionsProvider.getOptions(ClassWithCustomObjectFactory.class); + assertNotNull(options.objectFactory()); + assertEquals(TestObjectFactory.class, options.objectFactory()); + } + + @CucumberOptions() + private static final class ClassWithDefault {} + + @CucumberOptions(objectFactory = TestObjectFactory.class) + private static final class ClassWithCustomObjectFactory {} + + private static final class TestObjectFactory implements ObjectFactory { + @Override + public boolean addClass(Class glueClass) { + return false; + } + + @Override + public T getInstance(Class glueClass) { + return null; + } + + @Override + public void start() {} + + @Override + public void stop() {} + } +} diff --git a/testng/src/test/java/io/cucumber/testng/TestNGCucumberRunnerTest.java b/testng/src/test/java/io/cucumber/testng/TestNGCucumberRunnerTest.java index 083e8441ea..8c6de1c916 100644 --- a/testng/src/test/java/io/cucumber/testng/TestNGCucumberRunnerTest.java +++ b/testng/src/test/java/io/cucumber/testng/TestNGCucumberRunnerTest.java @@ -1,6 +1,6 @@ package io.cucumber.testng; -import cucumber.runtime.CucumberException; +import io.cucumber.core.exception.CucumberException; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; diff --git a/testng/src/test/java/io/cucumber/testng/TestPendingException.java b/testng/src/test/java/io/cucumber/testng/TestPendingException.java index 60a62c0507..354cdbcf1a 100644 --- a/testng/src/test/java/io/cucumber/testng/TestPendingException.java +++ b/testng/src/test/java/io/cucumber/testng/TestPendingException.java @@ -1,6 +1,6 @@ package io.cucumber.testng; -import cucumber.api.Pending; +import io.cucumber.core.backend.Pending; @Pending public final class TestPendingException extends RuntimeException { diff --git a/testng/src/test/resources/META-INF/services/io.cucumber.core.backend.BackendProviderService b/testng/src/test/resources/META-INF/services/io.cucumber.core.backend.BackendProviderService new file mode 100644 index 0000000000..a4f2c224f8 --- /dev/null +++ b/testng/src/test/resources/META-INF/services/io.cucumber.core.backend.BackendProviderService @@ -0,0 +1 @@ +io.cucumber.testng.StubBackendProviderService \ No newline at end of file diff --git a/weld/pom.xml b/weld/pom.xml index 72b881d54a..100c740f9a 100644 --- a/weld/pom.xml +++ b/weld/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 4.7.2-SNAPSHOT + 5.0.0-SNAPSHOT cucumber-weld diff --git a/weld/src/main/java/io/cucumber/weld/WeldFactory.java b/weld/src/main/java/io/cucumber/weld/WeldFactory.java index ec48f10e06..80f7b7c089 100644 --- a/weld/src/main/java/io/cucumber/weld/WeldFactory.java +++ b/weld/src/main/java/io/cucumber/weld/WeldFactory.java @@ -1,6 +1,6 @@ package io.cucumber.weld; -import cucumber.runtime.CucumberException; +import io.cucumber.core.exception.CucumberException; import io.cucumber.core.backend.ObjectFactory; import io.cucumber.core.logging.Logger; import io.cucumber.core.logging.LoggerFactory; diff --git a/weld/src/test/java/io/cucumber/weld/BellyStepdefs.java b/weld/src/test/java/io/cucumber/weld/BellyStepdefs.java index 879ce2af89..c4d5bb95f0 100644 --- a/weld/src/test/java/io/cucumber/weld/BellyStepdefs.java +++ b/weld/src/test/java/io/cucumber/weld/BellyStepdefs.java @@ -1,7 +1,7 @@ package io.cucumber.weld; -import cucumber.api.java.en.Given; -import cucumber.api.java.en.Then; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; import javax.inject.Inject; import javax.inject.Singleton; diff --git a/weld/src/test/java/io/cucumber/weld/UnusedGlue.java b/weld/src/test/java/io/cucumber/weld/UnusedGlue.java index 3a5a437ad3..e40b377fb2 100644 --- a/weld/src/test/java/io/cucumber/weld/UnusedGlue.java +++ b/weld/src/test/java/io/cucumber/weld/UnusedGlue.java @@ -1,7 +1,7 @@ package io.cucumber.weld; -import cucumber.api.java.Before; -import cucumber.api.java.en.Given; +import io.cucumber.java.Before; +import io.cucumber.java.en.Given; public class UnusedGlue { public UnusedGlue() { diff --git a/weld/src/test/java/io/cucumber/weld/WeldFactoryTest.java b/weld/src/test/java/io/cucumber/weld/WeldFactoryTest.java index 7d1482801a..62cb62be5b 100644 --- a/weld/src/test/java/io/cucumber/weld/WeldFactoryTest.java +++ b/weld/src/test/java/io/cucumber/weld/WeldFactoryTest.java @@ -1,8 +1,11 @@ package io.cucumber.weld; +import io.cucumber.core.exception.CucumberException; import io.cucumber.core.backend.ObjectFactory; + import io.cucumber.core.logging.LogRecordListener; import io.cucumber.core.logging.LoggerFactory; +import org.jboss.weld.environment.se.Weld; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -10,9 +13,13 @@ import org.junit.rules.ExpectedException; import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class WeldFactoryTest {