diff --git a/README.md b/README.md
index 74fdc992..c4247cdb 100644
--- a/README.md
+++ b/README.md
@@ -24,3 +24,44 @@ To use cucumber-jvm-scala in your project, add the following dependency to your
test
```
+
+## Compatibility matrix
+
+The Cucumber Scala version matches the Cucumber version except for the bugfix number
+which can be different.
+
+| Cucumber Scala version | Cucumber version | Scala versions |
+|------------------------|------------------|------------------|
+| 5.6.0 | 5.6.0 | 2.11, 2.12, 2.13 |
+| 4.7.1 | 4.7.1 | 2.11, 2.12, 2.13 |
+
+## Migrating from 4.x to 5.x
+
+If you are using Cucumber Scala 4.7.x and want to upgrade to 5.x, please note there are some major changes in addition to the Cucumber upgrade itself.
+
+### Packages
+
+All Cucumber Scala classes are now under `io.cucumber.scala` package instead of `cucumber.api.scala`.
+
+### Before/BeforeStep/After/AfterStep definitions
+
+The `Before`, `BeforeStep`, `After` and `AfterStep` definitions have slightly changed:
+- to apply only with some tags, the variable list of tags as `String*` is replaced by a single tag expression of type `String`.
+- if providing both an order and a tag expression, the order is now the second parameter instead of the first.
+This is more consistent with the Java implementation.
+
+For instance, the following code:
+
+```scala
+Before(1, "@tag1", "@tag2") { _ =>
+ // Do Something
+}
+```
+
+Is replaced by:
+
+```scala
+Before("@tag1 or @tag2", 1) { _ =>
+ // Do Something
+}
+```
\ No newline at end of file
diff --git a/examples/pom.xml b/examples/pom.xml
index 2285ef93..3eeb5679 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -4,7 +4,7 @@
io.cucumber
cucumber-jvm-scala
- 4.7.2-SNAPSHOT
+ 5.6.0-SNAPSHOT
scala-examples
diff --git a/examples/src/test/scala/cucumber/examples/scalacalculator/RpnCalculatorStepDefinitions.scala b/examples/src/test/scala/cucumber/examples/scalacalculator/RpnCalculatorStepDefinitions.scala
index a86e7ea3..e6f253b2 100644
--- a/examples/src/test/scala/cucumber/examples/scalacalculator/RpnCalculatorStepDefinitions.scala
+++ b/examples/src/test/scala/cucumber/examples/scalacalculator/RpnCalculatorStepDefinitions.scala
@@ -1,7 +1,6 @@
package cucumber.examples.scalacalculator
-import cucumber.api.Scenario
-import cucumber.api.scala.{ScalaDsl, EN}
+import io.cucumber.scala.{EN, ScalaDsl, Scenario}
import org.junit.Assert._
class RpnCalculatorStepDefinitions extends ScalaDsl with EN {
diff --git a/examples/src/test/scala/cucumber/examples/scalacalculator/RunCukesTest.scala b/examples/src/test/scala/cucumber/examples/scalacalculator/RunCukesTest.scala
index 37f2ba23..3b2684a2 100644
--- a/examples/src/test/scala/cucumber/examples/scalacalculator/RunCukesTest.scala
+++ b/examples/src/test/scala/cucumber/examples/scalacalculator/RunCukesTest.scala
@@ -1,8 +1,8 @@
package cucumber.examples.scalacalculator
-import cucumber.api.CucumberOptions
+import io.cucumber.junit.{Cucumber, CucumberOptions}
import org.junit.runner.RunWith
-import cucumber.api.junit.Cucumber
@RunWith(classOf[Cucumber])
+@CucumberOptions()
class RunCukesTest
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index cb096fc0..d9289c3e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,207 +1,208 @@
-
- 4.0.0
- io.cucumber
- cucumber-jvm-scala
- 4.7.2-SNAPSHOT
- pom
- Cucumber-JVM: Scala
- Cucumber for Scala
- http://cucumber.io/
-
-
- io.cucumber
- cucumber-parent
- 1.0.2
-
-
-
-
- glib-briia
- Glib Briia
- glib.briia@gmail.com
- AssertThat
- https://www.assertthat.com
-
-
-
-
- UTF-8
- UTF-8
- 3.3
- ${project.build.directory}
- 3.2.2
- 4.7.1
- 5.1.0
- 2.4.12
- 4.12
- 1.10.19
- 2.12.7
- 2.11.12
- 2.13.0
-
-
-
- scm:git:git://github.com/cucumber/cucumber-jvm-scala.git
- scm:git:git@github.com:cucumber/cucumber-jvm-scala.git
- git://github.com/cucumber/cucumber-jvm-scala.git
- v4.3.0
-
-
-
-
-
- io.cucumber
- cucumber-core
- ${cucumber.version}
-
-
- io.cucumber
- cucumber-junit
- ${cucumber.version}
- test
-
-
- junit
- junit
- ${junit.version}
- test
-
-
- org.mockito
- mockito-core
- ${mockito.version}
- test
-
-
-
-
-
- scala
-
-
-
-
- examples
-
- true
-
-
- examples
-
-
-
-
- coveralls.io
-
-
-
-
- org.codehaus.mojo
- cobertura-maven-plugin
- ${cobertura-maven-plugin.version}
-
-
- xml
-
-
- true
-
- true
-
-
-
-
-
- org.eluder.coveralls
- coveralls-maven-plugin
- 4.3.0
-
-
- ${basedir}/scala/scala_2.11/target/generated-sources/i18n
- ${basedir}/scala/scala_2.12/target/generated-sources/i18n
- ${basedir}/scala/scala_2.13/target/generated-sources/i18n
-
-
-
-
-
-
-
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-antrun-plugin
- 1.8
-
-
- org.codehaus.groovy
- groovy-all
- ${groovy.version}
-
-
- io.cucumber
- gherkin
- ${gherkin.version}
-
-
-
-
-
- org.apache.maven.plugins
- maven-javadoc-plugin
- 3.0.0-M1
-
-
- cucumber.runtime,cucumber.runtime.*
-
- http://docs.oracle.com/javase/7/docs/api/
- http://junit.sourceforge.net/javadoc/
-
-
-
- Main API Packages
- cucumber.api:cucumber.api.*
-
-
- I18n - Java
- cucumber.api.java.*
-
-
-
-
- java
-
-
-
-
- org.apache.maven.plugins
- maven-shade-plugin
- 3.0.0
-
-
- package
-
- shade
-
-
- true
-
-
-
-
-
-
-
- net.alchim31.maven
- scala-maven-plugin
- ${scala-maven-plugin.version}
-
-
-
-
-
-
+
+ 4.0.0
+ io.cucumber
+ cucumber-jvm-scala
+ 5.6.0-SNAPSHOT
+ pom
+ Cucumber-JVM: Scala
+ Cucumber for Scala
+ http://cucumber.io/
+
+
+ io.cucumber
+ cucumber-parent
+ 1.0.2
+
+
+
+
+ glib-briia
+ Glib Briia
+ glib.briia@gmail.com
+ AssertThat
+ https://www.assertthat.com
+
+
+
+
+ UTF-8
+ UTF-8
+ 3.3
+ ${project.build.directory}
+ 3.4.6
+ 5.6.0
+ 5.1.0
+ 2.4.19
+ 2.10.3
+ 4.12
+ 2.11.12
+ 2.12.11
+ 2.13.1
+ 1.13.9
+
+
+
+ scm:git:git://github.com/cucumber/cucumber-jvm-scala.git
+ scm:git:git@github.com:cucumber/cucumber-jvm-scala.git
+ git://github.com/cucumber/cucumber-jvm-scala.git
+ v4.3.0
+
+
+
+
+
+ io.cucumber
+ cucumber-core
+ ${cucumber.version}
+
+
+ io.cucumber
+ cucumber-junit
+ ${cucumber.version}
+ test
+
+
+ junit
+ junit
+ ${junit.version}
+ test
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson-databind.version}
+ test
+
+
+
+
+
+ scala
+
+
+
+
+ examples
+
+ true
+
+
+ examples
+
+
+
+
+ coveralls.io
+
+
+
+
+ org.codehaus.mojo
+ cobertura-maven-plugin
+ ${cobertura-maven-plugin.version}
+
+
+ xml
+
+
+ true
+
+ true
+
+
+
+
+
+ org.eluder.coveralls
+ coveralls-maven-plugin
+ 4.3.0
+
+
+ ${basedir}/scala/scala_2.11/target/generated-sources/i18n
+ ${basedir}/scala/scala_2.12/target/generated-sources/i18n
+ ${basedir}/scala/scala_2.13/target/generated-sources/i18n
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-antrun-plugin
+ 1.8
+
+
+ org.codehaus.groovy
+ groovy-all
+ ${groovy.version}
+
+
+ io.cucumber
+ gherkin
+ ${gherkin.version}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.0.0-M1
+
+
+ cucumber.runtime,cucumber.runtime.*
+
+ http://docs.oracle.com/javase/7/docs/api/
+ http://junit.sourceforge.net/javadoc/
+
+
+
+ Main API Packages
+ cucumber.api:cucumber.api.*
+
+
+ I18n - Java
+ cucumber.api.java.*
+
+
+
+
+ java
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.0.0
+
+
+ package
+
+ shade
+
+
+ true
+
+
+
+
+
+
+
+ net.alchim31.maven
+ scala-maven-plugin
+ ${scala-maven-plugin.version}
+
+
+
+
+
+
diff --git a/scala/pom.xml b/scala/pom.xml
index 8b0f00ad..d05f1c69 100644
--- a/scala/pom.xml
+++ b/scala/pom.xml
@@ -4,7 +4,7 @@
io.cucumber
cucumber-jvm-scala
- 4.7.2-SNAPSHOT
+ 5.6.0-SNAPSHOT
cucumber-scala-aggregator
@@ -43,6 +43,11 @@
junit
test
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ test
+
@@ -112,7 +117,7 @@ def unsupported = ["em"]
def dialectProvider = new GherkinDialectProvider()
def binding = ["dialectProvider":dialectProvider, "unsupported":unsupported]
template = engine.createTemplate(templateSource).make(binding)
-def file = new File(project.baseDir, "target${File.separator}generated-sources${File.separator}i18n${File.separator}cucumber${File.separator}api${File.separator}scala${File.separator}I18n.scala")
+def file = new File(project.baseDir, "target${File.separator}generated-sources${File.separator}i18n${File.separator}io${File.separator}cucumber${File.separator}scala${File.separator}I18n.scala")
file.parentFile.mkdirs()
file.write(template.toString(), "UTF-8")
]]>
diff --git a/scala/scala_2.11/pom.xml b/scala/scala_2.11/pom.xml
index 5b2325cc..06304078 100644
--- a/scala/scala_2.11/pom.xml
+++ b/scala/scala_2.11/pom.xml
@@ -4,7 +4,7 @@
io.cucumber
cucumber-scala-aggregator
- 4.7.2-SNAPSHOT
+ 5.6.0-SNAPSHOT
cucumber-scala_2.11
@@ -25,10 +25,22 @@
${scala.2.11.version}
test
+
+
+ org.mockito
+ mockito-scala_2.11
+ ${mockito-scala.version}
+ test
+
../sources/src/main/scala
+
+
+ ../sources/src/main/resources
+
+
../sources/src/test/scala
diff --git a/scala/scala_2.12/pom.xml b/scala/scala_2.12/pom.xml
index 1bfc3757..41651e9f 100644
--- a/scala/scala_2.12/pom.xml
+++ b/scala/scala_2.12/pom.xml
@@ -4,7 +4,7 @@
io.cucumber
cucumber-scala-aggregator
- 4.7.2-SNAPSHOT
+ 5.6.0-SNAPSHOT
cucumber-scala_2.12
@@ -25,10 +25,23 @@
${scala.2.12.version}
test
+
+
+ org.mockito
+ mockito-scala_2.12
+ ${mockito-scala.version}
+ test
+
+
../sources/src/main/scala
+
+
+ ../sources/src/main/resources
+
+
../sources/src/test/scala
diff --git a/scala/scala_2.13/pom.xml b/scala/scala_2.13/pom.xml
index baf9c24c..59c345ff 100644
--- a/scala/scala_2.13/pom.xml
+++ b/scala/scala_2.13/pom.xml
@@ -4,7 +4,7 @@
io.cucumber
cucumber-scala-aggregator
- 4.7.2-SNAPSHOT
+ 5.6.0-SNAPSHOT
cucumber-scala_2.13
@@ -25,10 +25,23 @@
${scala.2.13.version}
test
+
+
+ org.mockito
+ mockito-scala_2.13
+ ${mockito-scala.version}
+ test
+
+
../sources/src/main/scala
+
+
+ ../sources/src/main/resources
+
+
../sources/src/test/scala
diff --git a/scala/sources/src/main/code_generator/I18n.scala.txt b/scala/sources/src/main/code_generator/I18n.scala.txt
index ba02449b..1579c38a 100644
--- a/scala/sources/src/main/code_generator/I18n.scala.txt
+++ b/scala/sources/src/main/code_generator/I18n.scala.txt
@@ -1,4 +1,4 @@
-package cucumber.api.scala
+package io.cucumber.scala
<% gherkin.GherkinDialectProvider.DIALECTS.keySet().findAll { !unsupported.contains(it) }.each { language -> %>
trait ${language.replaceAll("[\\s-]", "_").toUpperCase()} {
diff --git a/scala/sources/src/main/resources/META-INF/services/io.cucumber.core.backend.BackendProviderService b/scala/sources/src/main/resources/META-INF/services/io.cucumber.core.backend.BackendProviderService
new file mode 100644
index 00000000..85d09655
--- /dev/null
+++ b/scala/sources/src/main/resources/META-INF/services/io.cucumber.core.backend.BackendProviderService
@@ -0,0 +1 @@
+io.cucumber.scala.ScalaBackendProviderService
\ No newline at end of file
diff --git a/scala/sources/src/main/scala/cucumber/runtime/scala/ScalaBackend.scala b/scala/sources/src/main/scala/cucumber/runtime/scala/ScalaBackend.scala
deleted file mode 100644
index 38629e38..00000000
--- a/scala/sources/src/main/scala/cucumber/runtime/scala/ScalaBackend.scala
+++ /dev/null
@@ -1,93 +0,0 @@
-package cucumber.runtime.scala
-
-import java.lang.reflect.Modifier
-import java.net.URI
-import java.util.{List => JList}
-
-import cucumber.api.scala.ScalaDsl
-import cucumber.runtime.{Backend, Glue}
-import cucumber.runtime.io.{ResourceLoader, ResourceLoaderClassFinder}
-import cucumber.runtime.snippets.{FunctionNameGenerator, SnippetGenerator}
-import gherkin.pickles.PickleStep
-import io.cucumber.stepexpression.TypeRegistry
-
-import scala.collection.JavaConverters._
-
-class ScalaBackend(resourceLoader: ResourceLoader, typeRegistry: TypeRegistry) extends Backend {
- private val snippetGenerator = new SnippetGenerator(new ScalaSnippetGenerator(), typeRegistry.parameterTypeRegistry())
- private var instances: Seq[ScalaDsl] = Nil
-
-
- def getStepDefinitions = instances.flatMap(_.getStepDefs(typeRegistry))
-
- def getBeforeHooks = instances.flatMap(_.beforeHooks)
-
- def getAfterHooks = instances.flatMap(_.afterHooks)
-
- def getAfterStepHooks = instances.flatMap(_.afterStepHooks)
-
- def getBeforeStepHooks = instances.flatMap(_.beforeStepHooks)
-
- def disposeWorld(): Unit = {
- instances = Nil
- }
-
- def getSnippet(step: PickleStep, keyword: String, functionNameGenerator: FunctionNameGenerator) = snippetGenerator.getSnippet(step, keyword, functionNameGenerator)
-
- def buildWorld(): Unit = {
- //I don't believe scala has to do anything to clean out its world
- }
-
- def loadGlue(glue: Glue, gluePaths: JList[URI]): Unit = {
-
- val cl = Thread.currentThread().getContextClassLoader
- val classFinder = new ResourceLoaderClassFinder(resourceLoader, cl)
- val packages = gluePaths.asScala
- val dslClasses = packages flatMap {
- classFinder.getDescendants(classOf[ScalaDsl], _).asScala
- } filter { cls =>
- try {
- cls.getDeclaredConstructor()
- true
- } catch {
- case e: Throwable => false
- }
- }
-
- val (clsClasses, objClasses) = dslClasses partition { cls =>
- try {
- Modifier.isPublic(cls.getConstructor().getModifiers)
- } catch {
- case e: Throwable => false
- }
- }
- val objInstances = objClasses map { cls =>
- val instField = cls.getDeclaredField("MODULE$")
- instField.setAccessible(true)
- instField.get(null).asInstanceOf[ScalaDsl]
- }
- val clsInstances = clsClasses.map {
- _.newInstance()
- }
-
- instances = objInstances.toSeq ++ clsInstances
-
- getStepDefinitions map {
- glue.addStepDefinition(_)
- }
- getBeforeHooks map {
- glue.addBeforeHook(_)
- }
- getAfterHooks map {
- glue.addAfterHook(_)
- }
- getAfterStepHooks map {
- glue.addAfterStepHook(_)
- }
- getBeforeStepHooks map {
- glue.addBeforeStepHook(_)
- }
- ()
- }
-
-}
diff --git a/scala/sources/src/main/scala/cucumber/runtime/scala/ScalaHookDefinition.scala b/scala/sources/src/main/scala/cucumber/runtime/scala/ScalaHookDefinition.scala
deleted file mode 100644
index 89a4bb16..00000000
--- a/scala/sources/src/main/scala/cucumber/runtime/scala/ScalaHookDefinition.scala
+++ /dev/null
@@ -1,27 +0,0 @@
-package cucumber.runtime.scala
-
-import gherkin.pickles.PickleTag
-import java.util.Collection
-
-import cucumber.api.Scenario
-import cucumber.runtime.HookDefinition
-import cucumber.runtime.filter.TagPredicate
-
-import scala.collection.JavaConverters._
-
-class ScalaHookDefinition(f:Scenario => Unit,
- order:Int,
- tags:Seq[String]) extends HookDefinition {
-
- val tagPredicate = new TagPredicate(tags.asJava)
-
- def getLocation(detail: Boolean) = "TODO: Implement getLocation in similar fashion to ScalaStepDefinition"
-
- def execute(scenario: Scenario): Unit = f(scenario)
-
- def matches(tags: Collection[PickleTag]) = tagPredicate.apply(tags)
-
- def getOrder = order
-
- def isScenarioScoped = false
-}
diff --git a/scala/sources/src/main/scala/cucumber/runtime/scala/ScalaSnippetGenerator.scala b/scala/sources/src/main/scala/cucumber/runtime/scala/ScalaSnippetGenerator.scala
deleted file mode 100644
index 92a8c3c8..00000000
--- a/scala/sources/src/main/scala/cucumber/runtime/scala/ScalaSnippetGenerator.scala
+++ /dev/null
@@ -1,42 +0,0 @@
-package cucumber.runtime.scala
-
-import java.lang.reflect.Type
-import java.util
-
-import cucumber.runtime.snippets.Snippet
-
-import collection.JavaConverters._
-
-class ScalaSnippetGenerator extends Snippet {
-
- def template() =
- "{0}(\"\"\"{1}\"\"\")'{' ({3}) =>\n" +
- " //// {4}\n" +
- " throw new PendingException()\n" +
- "'}'"
-
- def tableHint() = null
-
- def namedGroupStart() = null
-
- def namedGroupEnd() = null
-
- def escapePattern(pattern:String) = pattern
-
- override def arguments(map: util.Map[String, Type]): String = {
- val indexed = map.asScala.zipWithIndex
-
- def name(clazz: Class[_]) =
- if(clazz.isPrimitive){
- val name = clazz.getName
- s"${name.charAt(0).toUpper}${name.substring(1)}"
- } else
- clazz.getSimpleName
-
- val named = indexed.map {
- case (c, i) => "arg" + i + ":" + name(c._2.getClass)
- }
-
- named.mkString(", ")
- }
-}
diff --git a/scala/sources/src/main/scala/cucumber/runtime/scala/ScalaStepDefinition.scala b/scala/sources/src/main/scala/cucumber/runtime/scala/ScalaStepDefinition.scala
deleted file mode 100644
index c7bfe0c1..00000000
--- a/scala/sources/src/main/scala/cucumber/runtime/scala/ScalaStepDefinition.scala
+++ /dev/null
@@ -1,92 +0,0 @@
-package cucumber.runtime.scala
-
-import java.lang.reflect.Type
-import cucumber.runtime.StepDefinition
-import gherkin.pickles.PickleStep
-import io.cucumber.stepexpression._
-
-/**
- * Implementation of step definition for scala.
- *
- * @param frame Representation of a stack frame containing information about the context in which a
- * step was defined. Allows retrospective queries about the definition of a step.
- *
- * @param name The name of the step definition class, e.g. cucumber.runtime.scala.test.CukesStepDefinitions
- *
- * @param pattern The regex matcher that defines the cucumber step, e.g. /I eat (.*) cukes$/
-
- * @param parameterInfos
- *
- * @param f Function body of a step definition. This is what actually runs the code within the step def.
- */
-class ScalaStepDefinition(frame:StackTraceElement,
- name:String,
- pattern:String,
- parameterInfos:Array[Type],
- f:List[Any] => Any) extends StepDefinition {
-
- private[cucumber] var typeRegistry: TypeRegistry = null
- private var expression: StepExpression = null
-
- /**
- * 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.
- */
- def matchedArguments(step: PickleStep) = {
- expression = createExpression(pattern, typeRegistry)
- val argumentMatcher = new ExpressionArgumentMatcher(expression)
- argumentMatcher.argumentsFrom(step, parameterInfos:_*)
- }
-
- private def createExpression(expression: String, typeRegistry: TypeRegistry): StepExpression = {
- if (parameterInfos.isEmpty) new StepExpressionFactory(typeRegistry).createExpression(expression)
- else {
- val parameterInfo = parameterInfos(parameterInfos.size - 1)
- new StepExpressionFactory(typeRegistry).createExpression(expression, parameterInfo, false)
- }
- }
-
- /**
- * 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.
- */
- override def getLocation(detail: Boolean) = frame.getFileName + ":" + frame.getLineNumber
-
- /**
- * How many declared parameters this stepdefinition has. Returns null if unknown.
- */
- override def getParameterCount() = parameterInfos.size
-
- /**
- * The parameter type at index n. A hint about the raw parameter type is passed to make
- * it easier for the implementation to make a guess based on runtime information.
- * As Scala is a statically typed language, the javaType parameter is ignored
- */
- def getParameterType(index: Int, javaType: Type) = {
- parameterInfos(index)
- }
-
- /**
- * Invokes the step definition. The method should raise a Throwable
- * if the invocation fails, which will cause the step to fail.
- */
- override def execute(args: Array[AnyRef]): Unit = {
- f(args.toList)
- }
-
- /**
- * Return true if this matches the location. This is used to filter
- * stack traces.
- */
- override def isDefinedAt(stackTraceElement: StackTraceElement) = stackTraceElement == frame
-
- /**
- * @return the pattern associated with this instance. Used for error reporting only.
- */
- override def getPattern = pattern
-
- override def isScenarioScoped = false
-}
diff --git a/scala/sources/src/main/scala/io/cucumber/scala/AbstractGlueDefinition.scala b/scala/sources/src/main/scala/io/cucumber/scala/AbstractGlueDefinition.scala
new file mode 100644
index 00000000..9813d39e
--- /dev/null
+++ b/scala/sources/src/main/scala/io/cucumber/scala/AbstractGlueDefinition.scala
@@ -0,0 +1,15 @@
+package io.cucumber.scala
+
+import io.cucumber.core.backend.Located
+
+abstract class AbstractGlueDefinition(location: StackTraceElement) extends Located {
+
+ def getLocation(): String = {
+ location.toString
+ }
+
+ def isDefinedAt(stackTraceElement: StackTraceElement): Boolean = {
+ location.getFileName != null && location.getFileName == stackTraceElement.getFileName
+ }
+
+}
diff --git a/scala/sources/src/main/scala/io/cucumber/scala/Aliases.scala b/scala/sources/src/main/scala/io/cucumber/scala/Aliases.scala
new file mode 100644
index 00000000..ff588ef3
--- /dev/null
+++ b/scala/sources/src/main/scala/io/cucumber/scala/Aliases.scala
@@ -0,0 +1,12 @@
+package io.cucumber.scala
+
+/**
+ * Contains some aliases to help match this codebase with cucumber-java
+ */
+object Aliases {
+
+ type HookBody = Scenario => Unit
+
+ type StepDefinitionBody = () => Unit
+
+}
diff --git a/scala/sources/src/main/scala/io/cucumber/scala/GlueAdaptor.scala b/scala/sources/src/main/scala/io/cucumber/scala/GlueAdaptor.scala
new file mode 100644
index 00000000..8d910885
--- /dev/null
+++ b/scala/sources/src/main/scala/io/cucumber/scala/GlueAdaptor.scala
@@ -0,0 +1,15 @@
+package io.cucumber.scala
+
+import io.cucumber.core.backend.Glue
+
+class GlueAdaptor(glue: Glue) {
+
+ def addDefinition(registry: ScalaDslRegistry): Unit = {
+ registry.stepDefinitions.foreach(glue.addStepDefinition)
+ registry.beforeHooks.foreach(glue.addBeforeHook)
+ registry.afterHooks.foreach(glue.addAfterHook)
+ registry.afterStepHooks.foreach(glue.addAfterStepHook)
+ registry.beforeStepHooks.foreach(glue.addBeforeStepHook)
+ }
+
+}
diff --git a/scala/sources/src/main/scala/io/cucumber/scala/PendingException.scala b/scala/sources/src/main/scala/io/cucumber/scala/PendingException.scala
new file mode 100644
index 00000000..021884aa
--- /dev/null
+++ b/scala/sources/src/main/scala/io/cucumber/scala/PendingException.scala
@@ -0,0 +1,5 @@
+package io.cucumber.scala
+
+class PendingException extends RuntimeException("TODO: implement me") {
+
+}
diff --git a/scala/sources/src/main/scala/io/cucumber/scala/ScalaBackend.scala b/scala/sources/src/main/scala/io/cucumber/scala/ScalaBackend.scala
new file mode 100644
index 00000000..ae943dae
--- /dev/null
+++ b/scala/sources/src/main/scala/io/cucumber/scala/ScalaBackend.scala
@@ -0,0 +1,70 @@
+package io.cucumber.scala
+
+import java.lang.reflect.Modifier
+import java.net.URI
+import java.util.function.Supplier
+import java.util.{List => JList}
+
+import io.cucumber.core.backend._
+import io.cucumber.core.resource.{ClasspathScanner, ClasspathSupport}
+
+import scala.collection.JavaConverters._
+import scala.util.Try
+
+class ScalaBackend(classLoaderProvider: Supplier[ClassLoader]) extends Backend {
+
+ private val classFinder = new ClasspathScanner(classLoaderProvider)
+
+ private[scala] var scalaGlueInstances: Seq[ScalaDsl] = Nil
+
+ override def disposeWorld(): Unit = {
+ scalaGlueInstances = Nil
+ }
+
+ override def getSnippet(): Snippet = {
+ new ScalaSnippet()
+ }
+
+ override def buildWorld(): Unit = {
+ // Nothing to do
+ }
+
+ override def loadGlue(glue: Glue, gluePaths: JList[URI]): Unit = {
+
+ val dslClasses = gluePaths.asScala
+ .filter(gluePath => ClasspathSupport.CLASSPATH_SCHEME.equals(gluePath.getScheme))
+ .map(ClasspathSupport.packageName)
+ .flatMap(basePackageName => classFinder.scanForSubClassesInPackage(basePackageName, classOf[ScalaDsl]).asScala)
+ .filter(glueClass => !glueClass.isInterface)
+ .filter(glueClass => glueClass.getConstructors.length > 0)
+
+ val (clsClasses, objClasses) = dslClasses.partition(isRegularClass)
+
+ val objInstances = objClasses.map { cls =>
+ val instField = cls.getDeclaredField("MODULE$")
+ instField.setAccessible(true)
+ instField.get(null).asInstanceOf[ScalaDsl]
+ }
+ val clsInstances = clsClasses.map {
+ _.newInstance()
+ }
+
+ // FIXME we should not create instances above but fill the container like Cucumber Java does
+ // https://github.com/cucumber/cucumber-jvm-scala/issues/1
+ //clsClasses.foreach(container.addClass(_))
+ scalaGlueInstances = objInstances.toSeq ++ clsInstances
+
+ val glueAdaptor = new GlueAdaptor(glue)
+
+ scalaGlueInstances.foreach { glueInstance =>
+ glueAdaptor.addDefinition(glueInstance.registry)
+ }
+
+ ()
+ }
+
+ private def isRegularClass(cls: Class[_]): Boolean = {
+ Try(Modifier.isPublic(cls.getConstructor().getModifiers)).getOrElse(false)
+ }
+
+}
diff --git a/scala/sources/src/main/scala/io/cucumber/scala/ScalaBackendProviderService.scala b/scala/sources/src/main/scala/io/cucumber/scala/ScalaBackendProviderService.scala
new file mode 100644
index 00000000..0a3fdec3
--- /dev/null
+++ b/scala/sources/src/main/scala/io/cucumber/scala/ScalaBackendProviderService.scala
@@ -0,0 +1,13 @@
+package io.cucumber.scala
+
+import java.util.function.Supplier
+
+import io.cucumber.core.backend.{Backend, BackendProviderService, Container, Lookup}
+
+class ScalaBackendProviderService extends BackendProviderService {
+
+ override def create(lookup: Lookup, container: Container, classLoaderSupplier: Supplier[ClassLoader]): Backend = {
+ new ScalaBackend(classLoaderSupplier)
+ }
+
+}
diff --git a/scala/sources/src/main/scala/cucumber/api/scala/ScalaDsl.scala b/scala/sources/src/main/scala/io/cucumber/scala/ScalaDsl.scala
similarity index 89%
rename from scala/sources/src/main/scala/cucumber/api/scala/ScalaDsl.scala
rename to scala/sources/src/main/scala/io/cucumber/scala/ScalaDsl.scala
index 0290a743..b6ba62c3 100644
--- a/scala/sources/src/main/scala/cucumber/api/scala/ScalaDsl.scala
+++ b/scala/sources/src/main/scala/io/cucumber/scala/ScalaDsl.scala
@@ -1,83 +1,90 @@
-package cucumber.api.scala
+package io.cucumber.scala
import java.lang.reflect.{ParameterizedType, Type}
-import cucumber.api.Scenario
-import cucumber.runtime.{HookDefinition, StepDefinition}
-import cucumber.runtime.scala.{ScalaHookDefinition, ScalaStepDefinition}
-import io.cucumber.stepexpression.TypeRegistry
+import io.cucumber.core.backend.{HookDefinition, StepDefinition}
+import io.cucumber.scala.Aliases.HookBody
import scala.collection.mutable.ArrayBuffer
-
/**
- * Base trait for a scala step definition implementation.
- */
+ * Base trait for a scala step definition implementation.
+ */
trait ScalaDsl {
self =>
+
+ val EMPTY_TAG_EXPRESSION = ""
+ val DEFAULT_BEFORE_ORDER = 1000
+ val DEFAULT_AFTER_ORDER = 1000
+
import scala.language.implicitConversions
- private val stepDefinitions = new ArrayBuffer[StepDefinition]
+ val registry = new ScalaDslRegistry()
- private[cucumber] val beforeHooks = new ArrayBuffer[HookDefinition]
+ // TODO support Before/After with no parameter
- private[cucumber] val beforeStepHooks = new ArrayBuffer[HookDefinition]
+ def Before(body: HookBody): Unit = {
+ Before(EMPTY_TAG_EXPRESSION, DEFAULT_BEFORE_ORDER)(body)
+ }
- private[cucumber] val afterHooks = new ArrayBuffer[HookDefinition]
+ def Before(tagExpression: String)(body: HookBody): Unit = {
+ Before(tagExpression, DEFAULT_BEFORE_ORDER)(body)
+ }
- private[cucumber] val afterStepHooks = new ArrayBuffer[HookDefinition]
+ def Before(order: Int)(body: HookBody): Unit = {
+ Before(EMPTY_TAG_EXPRESSION, order)(body)
+ }
- private[cucumber] def getStepDefs(typeRegistry: TypeRegistry): ArrayBuffer[StepDefinition]={
- stepDefinitions.foreach(d=>d.asInstanceOf[ScalaStepDefinition].typeRegistry = typeRegistry)
- stepDefinitions
+ def Before(tagExpression: String, order: Int)(body: HookBody): Unit = {
+ registry.beforeHooks += new ScalaHookDefinition(tagExpression, order, body)
}
- def Before(f: Scenario => Unit) : Unit = {
- Before()(f)
+ def BeforeStep(body: HookBody): Unit = {
+ BeforeStep(EMPTY_TAG_EXPRESSION, DEFAULT_BEFORE_ORDER)(body)
}
- def Before(tags: String*)(f: Scenario => Unit) : Unit = {
- Before(Int.MaxValue, tags: _*)(f)
+ def BeforeStep(tagExpression: String)(body: HookBody): Unit = {
+ BeforeStep(tagExpression, DEFAULT_BEFORE_ORDER)(body)
}
- def Before(order: Int, tags: String*)(f: Scenario => Unit) : Unit = {
- beforeHooks += new ScalaHookDefinition(f, order, tags)
+ def BeforeStep(order: Int)(body: HookBody): Unit = {
+ BeforeStep(EMPTY_TAG_EXPRESSION, order)(body)
}
- def BeforeStep(f: Scenario => Unit) : Unit = {
- BeforeStep()(f)
+ def BeforeStep(tagExpression: String, order: Int)(body: HookBody): Unit = {
+ registry.beforeStepHooks += new ScalaHookDefinition(tagExpression, order, body)
}
- def BeforeStep(tags: String*)(f: Scenario => Unit) : Unit = {
- BeforeStep(Int.MaxValue, tags: _*)(f)
+ def After(body: HookBody): Unit = {
+ After(EMPTY_TAG_EXPRESSION, DEFAULT_AFTER_ORDER)(body)
}
- def BeforeStep(order: Int, tags: String*)(f: Scenario => Unit) : Unit = {
- beforeStepHooks += new ScalaHookDefinition(f, order, tags)
+ def After(tagExpression: String)(body: HookBody): Unit = {
+ After(tagExpression, DEFAULT_AFTER_ORDER)(body)
}
- def After(f: Scenario => Unit) : Unit = {
- After()(f)
+ def After(order: Int)(body: HookBody): Unit = {
+ After(EMPTY_TAG_EXPRESSION, order)(body)
}
- def After(tags: String*)(f: Scenario => Unit) : Unit = {
- After(Int.MaxValue, tags: _*)(f)
+ def After(tagExpression: String, order: Int)(body: HookBody): Unit = {
+ registry.afterHooks += new ScalaHookDefinition(tagExpression, order, body)
}
- def After(order: Int, tags: String*)(f: Scenario => Unit) : Unit = {
- afterHooks += new ScalaHookDefinition(f, order, tags)
+ def AfterStep(body: HookBody): Unit = {
+ AfterStep(EMPTY_TAG_EXPRESSION, DEFAULT_AFTER_ORDER)(body)
}
- def AfterStep(f: Scenario => Unit) : Unit = {
- AfterStep()(f)
+ def AfterStep(tagExpression: String)(body: HookBody): Unit = {
+ AfterStep(tagExpression, DEFAULT_AFTER_ORDER)(body)
}
- def AfterStep(tags: String*)(f: Scenario => Unit) : Unit = {
- AfterStep(Int.MaxValue, tags: _*)(f)
+ def AfterStep(order: Int)(body: HookBody): Unit = {
+ AfterStep(EMPTY_TAG_EXPRESSION, order)(body)
}
- def AfterStep(order: Int, tags: String*)(f: Scenario => Unit) : Unit = {
- afterStepHooks += new ScalaHookDefinition(f, order, tags)
+ def AfterStep(tagExpression: String, order: Int)(body: HookBody): Unit = {
+ registry.afterStepHooks += new ScalaHookDefinition(tagExpression, order, body)
}
@@ -88,7 +95,7 @@ trait ScalaDsl {
final class Fun0(val f: Function0[Any])
object Fun0 {
- implicit def function0AsFun0(f: Function0[Any]) = new Fun0(f)
+ implicit def function0AsFun0(f: Function0[Any]): Fun0 = new Fun0(f)
}
final class StepBody(name: String, regex: String) {
@@ -115,11 +122,11 @@ trait ScalaDsl {
* Fun0 and the implicit conversion lets us work around this.
*
**/
- def apply(f: => Unit) : Unit = {
+ def apply(f: => Unit): Unit = {
apply(f _)
}
- def apply(fun: Fun0) : Unit = {
+ def apply(fun: Fun0): Unit = {
register() {
case Nil => fun.f()
}
@@ -338,8 +345,8 @@ trait ScalaDsl {
}
}
- def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16](f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16) => Any)(implicit m1: Manifest[T1], m2: Manifest[T2], m3: Manifest[T3], m4: Manifest[T4], m5: Manifest[T5], m6: Manifest[T6], m7: Manifest[T7], m8: Manifest[T8], m9: Manifest[T9], m10: Manifest[T10], m11: Manifest[T11], m12: Manifest[T12], m13: Manifest[T13], m14: Manifest[T14], m15: Manifest[T15], m16: Manifest[T16]) = {
- register(m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16) {
+ def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16](f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16) => Any)(implicit m1: Manifest[T1], m2: Manifest[T2], m3: Manifest[T3], m4: Manifest[T4], m5: Manifest[T5], m6: Manifest[T6], m7: Manifest[T7], m8: Manifest[T8], m9: Manifest[T9], m10: Manifest[T10], m11: Manifest[T11], m12: Manifest[T12], m13: Manifest[T13], m14: Manifest[T14], m15: Manifest[T15], m16: Manifest[T16]) = {
+ register(m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16) {
case List(a1: AnyRef, a2: AnyRef, a3: AnyRef, a4: AnyRef, a5: AnyRef, a6: AnyRef, a7: AnyRef, a8: AnyRef, a9: AnyRef, a10: AnyRef, a11: AnyRef, a12: AnyRef, a13: AnyRef, a14: AnyRef, a15: AnyRef, a16: AnyRef) =>
f(a1.asInstanceOf[T1],
a2.asInstanceOf[T2],
@@ -486,7 +493,7 @@ trait ScalaDsl {
}
def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22](f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22) => Any)(implicit m1: Manifest[T1], m2: Manifest[T2], m3: Manifest[T3], m4: Manifest[T4], m5: Manifest[T5], m6: Manifest[T6], m7: Manifest[T7], m8: Manifest[T8], m9: Manifest[T9], m10: Manifest[T10], m11: Manifest[T11], m12: Manifest[T12], m13: Manifest[T13], m14: Manifest[T14], m15: Manifest[T15], m16: Manifest[T16], m17: Manifest[T17], m18: Manifest[T18], m19: Manifest[T19], m20: Manifest[T20], m21: Manifest[T21], m22: Manifest[T22]) = {
- register(m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20, m21, m22) {
+ register(m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20, m21, m22) {
case List(a1: AnyRef, a2: AnyRef, a3: AnyRef, a4: AnyRef, a5: AnyRef, a6: AnyRef, a7: AnyRef, a8: AnyRef, a9: AnyRef, a10: AnyRef, a11: AnyRef, a12: AnyRef, a13: AnyRef, a14: AnyRef, a15: AnyRef, a16: AnyRef, a17: AnyRef, a18: AnyRef, a19: AnyRef, a20: AnyRef, a21: AnyRef, a22: AnyRef) =>
f(a1.asInstanceOf[T1],
a2.asInstanceOf[T2],
@@ -513,9 +520,9 @@ trait ScalaDsl {
}
}
- private def register(ms: Manifest[_ <: Any]*)(pf: PartialFunction[List[Any], Any]) = {
+ private def register(ms: Manifest[_ <: Any]*)(pf: PartialFunction[List[Any], Any]): Unit = {
val types = ms.map(m => toJavaType(m)).toArray
- stepDefinitions += new ScalaStepDefinition(frame, name, regex, types, pf)
+ registry.stepDefinitions += new ScalaStepDefinition(Utils.frame(self), name, regex, types, pf)
}
private def toJavaType(m: Manifest[_]): Type = {
@@ -524,7 +531,7 @@ trait ScalaDsl {
m.runtimeClass
} else {
new ParameterizedType {
- override def getActualTypeArguments: Array[Type] = typeArgs.map(toJavaType(_)).toArray
+ override def getActualTypeArguments: Array[Type] = typeArgs.map(toJavaType).toArray
override def getRawType: Type = m.runtimeClass
@@ -534,14 +541,4 @@ trait ScalaDsl {
}
}
- /**
- * Return the stack frame to allow us to identify where in a step definition file
- * we are currently based
- */
- private def frame: StackTraceElement = {
- val frames = Thread.currentThread().getStackTrace
- val currentClass = self.getClass.getName
- frames.find(_.getClassName == currentClass).get
- }
-
}
diff --git a/scala/sources/src/main/scala/io/cucumber/scala/ScalaDslRegistry.scala b/scala/sources/src/main/scala/io/cucumber/scala/ScalaDslRegistry.scala
new file mode 100644
index 00000000..331e5b93
--- /dev/null
+++ b/scala/sources/src/main/scala/io/cucumber/scala/ScalaDslRegistry.scala
@@ -0,0 +1,20 @@
+package io.cucumber.scala
+
+import io.cucumber.core.backend.{HookDefinition, StepDefinition}
+
+import scala.collection.mutable.ArrayBuffer
+
+class ScalaDslRegistry {
+
+ val stepDefinitions = new ArrayBuffer[StepDefinition]
+
+ val beforeHooks = new ArrayBuffer[HookDefinition]
+
+ val beforeStepHooks = new ArrayBuffer[HookDefinition]
+
+ val afterHooks = new ArrayBuffer[HookDefinition]
+
+ val afterStepHooks = new ArrayBuffer[HookDefinition]
+
+}
+
diff --git a/scala/sources/src/main/scala/io/cucumber/scala/ScalaHookDefinition.scala b/scala/sources/src/main/scala/io/cucumber/scala/ScalaHookDefinition.scala
new file mode 100644
index 00000000..0b8208fb
--- /dev/null
+++ b/scala/sources/src/main/scala/io/cucumber/scala/ScalaHookDefinition.scala
@@ -0,0 +1,18 @@
+package io.cucumber.scala
+
+import io.cucumber.core.backend.{HookDefinition, TestCaseState}
+import io.cucumber.scala.Aliases.HookBody
+
+class ScalaHookDefinition(tagExpression: String, order: Int, body: HookBody)
+ extends AbstractGlueDefinition(new Exception().getStackTrace()(3))
+ with HookDefinition {
+
+ override def execute(state: TestCaseState): Unit = {
+ body(new Scenario(state))
+ }
+
+ override def getTagExpression: String = tagExpression
+
+ override def getOrder: Int = order
+
+}
diff --git a/scala/sources/src/main/scala/io/cucumber/scala/ScalaParameterInfo.scala b/scala/sources/src/main/scala/io/cucumber/scala/ScalaParameterInfo.scala
new file mode 100644
index 00000000..0e509637
--- /dev/null
+++ b/scala/sources/src/main/scala/io/cucumber/scala/ScalaParameterInfo.scala
@@ -0,0 +1,15 @@
+package io.cucumber.scala
+
+import java.lang.reflect.Type
+
+import io.cucumber.core.backend.{ParameterInfo, TypeResolver}
+
+class ScalaParameterInfo(typeResolver: ScalaTypeResolver) extends ParameterInfo {
+
+ override def getType: Type = typeResolver.`type`
+
+ override def isTransposed: Boolean = false
+
+ override def getTypeResolver: TypeResolver = typeResolver
+
+}
diff --git a/scala/sources/src/main/scala/io/cucumber/scala/ScalaSnippet.scala b/scala/sources/src/main/scala/io/cucumber/scala/ScalaSnippet.scala
new file mode 100644
index 00000000..3e6a7e97
--- /dev/null
+++ b/scala/sources/src/main/scala/io/cucumber/scala/ScalaSnippet.scala
@@ -0,0 +1,63 @@
+package io.cucumber.scala
+
+import java.lang.reflect.Type
+import java.text.MessageFormat
+import java.util.{Map => JMap}
+
+import io.cucumber.core.backend.Snippet
+import io.cucumber.datatable.DataTable
+
+import scala.collection.JavaConverters._
+
+object ScalaSnippet {
+
+ // Allows to use """ in """xxx"""" strings
+ val tripleDoubleQuotes = "\"\"\""
+
+}
+
+class ScalaSnippet extends Snippet {
+
+ import ScalaSnippet.tripleDoubleQuotes
+
+ override def template(): MessageFormat = {
+ new MessageFormat(
+ s"""{0}(${tripleDoubleQuotes}{1}${tripleDoubleQuotes}) '{' ({3}) =>
+ | // {4}
+ | throw new ${classOf[PendingException].getName}()
+ |'}'""".stripMargin)
+ }
+
+ override def tableHint(): String = {
+ """| // For automatic transformation, change DataTable to one of
+ | // E, List, List>, List