From 7a085ca0d8335c858b6082b48b60173ddf19cc90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Jourdan-Weil?= Date: Sat, 25 Apr 2020 18:27:44 +0200 Subject: [PATCH 1/6] Make 2.13 the default module --- scala/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scala/pom.xml b/scala/pom.xml index 421c42cb..efc7e796 100644 --- a/scala/pom.xml +++ b/scala/pom.xml @@ -13,7 +13,7 @@ scala_2.11 - scala_2.13 + scala_2.12 @@ -23,7 +23,7 @@ 1.8 - scala_2.12 + scala_2.13 From 79f4ac2a6bd7906e5ff7d06951565eb709a7af09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Jourdan-Weil?= Date: Sun, 26 Apr 2020 14:47:59 +0200 Subject: [PATCH 2/6] Add failing tests for isolation and objects --- .../resources/tests/{ => cukes}/cukes.feature | 0 .../resources/tests/isolated/isolated.feature | 12 +++++++++ .../tests/isolated/isolated2.feature | 8 ++++++ .../resources/tests/object/object.feature | 11 ++++++++ .../tests/{ => cukes}/RunCukesTest.scala | 2 +- .../scala/tests/{ => cukes}/StepDefs.scala | 4 +-- .../TypeRegistryConfiguration.scala | 4 +-- .../scala/tests/{ => cukes}/model/Cuke.scala | 2 +- .../tests/{ => cukes}/model/Person.scala | 2 +- .../scala/tests/{ => cukes}/model/Snake.scala | 2 +- .../scala/tests/isolated/IsolatedSteps.scala | 26 ++++++++++++++++++ .../tests/isolated/RunIsolatedTest.scala | 8 ++++++ .../test/scala/tests/object/ObjectSteps.scala | 27 +++++++++++++++++++ .../scala/tests/object/RunObjectTest.scala | 8 ++++++ 14 files changed, 108 insertions(+), 8 deletions(-) rename scala/sources/src/test/resources/tests/{ => cukes}/cukes.feature (100%) create mode 100644 scala/sources/src/test/resources/tests/isolated/isolated.feature create mode 100644 scala/sources/src/test/resources/tests/isolated/isolated2.feature create mode 100644 scala/sources/src/test/resources/tests/object/object.feature rename scala/sources/src/test/scala/tests/{ => cukes}/RunCukesTest.scala (89%) rename scala/sources/src/test/scala/tests/{ => cukes}/StepDefs.scala (98%) rename scala/sources/src/test/scala/tests/{ => cukes}/TypeRegistryConfiguration.scala (97%) rename scala/sources/src/test/scala/tests/{ => cukes}/model/Cuke.scala (63%) rename scala/sources/src/test/scala/tests/{ => cukes}/model/Person.scala (85%) rename scala/sources/src/test/scala/tests/{ => cukes}/model/Snake.scala (90%) create mode 100644 scala/sources/src/test/scala/tests/isolated/IsolatedSteps.scala create mode 100644 scala/sources/src/test/scala/tests/isolated/RunIsolatedTest.scala create mode 100644 scala/sources/src/test/scala/tests/object/ObjectSteps.scala create mode 100644 scala/sources/src/test/scala/tests/object/RunObjectTest.scala diff --git a/scala/sources/src/test/resources/tests/cukes.feature b/scala/sources/src/test/resources/tests/cukes/cukes.feature similarity index 100% rename from scala/sources/src/test/resources/tests/cukes.feature rename to scala/sources/src/test/resources/tests/cukes/cukes.feature diff --git a/scala/sources/src/test/resources/tests/isolated/isolated.feature b/scala/sources/src/test/resources/tests/isolated/isolated.feature new file mode 100644 index 00000000..eb5dc9a8 --- /dev/null +++ b/scala/sources/src/test/resources/tests/isolated/isolated.feature @@ -0,0 +1,12 @@ +Feature: Isolated + + Scenario: First test + Given I set the list of values to + | 1 | + | 2 | + | 3 | + And I multiply by 2 + Then the list of values is + | 2 | + | 4 | + | 6 | \ No newline at end of file diff --git a/scala/sources/src/test/resources/tests/isolated/isolated2.feature b/scala/sources/src/test/resources/tests/isolated/isolated2.feature new file mode 100644 index 00000000..90d608d8 --- /dev/null +++ b/scala/sources/src/test/resources/tests/isolated/isolated2.feature @@ -0,0 +1,8 @@ +Feature: Isolated 2 + + Scenario: Second test + Given I set the list of values to + | 10 | + And I multiply by 2 + Then the list of values is + | 20 | \ No newline at end of file diff --git a/scala/sources/src/test/resources/tests/object/object.feature b/scala/sources/src/test/resources/tests/object/object.feature new file mode 100644 index 00000000..e60efad8 --- /dev/null +++ b/scala/sources/src/test/resources/tests/object/object.feature @@ -0,0 +1,11 @@ +Feature: As Cucumber Scala, I want to be able to use steps defined in objects even though they will persist their state across scenarios + + Scenario: First scenario + Given I have a calculator + When I do 2 + 2 + Then I got 4 + + Scenario: Second scenario + Given I have a calculator + When I do 5 + 6 + Then I got 11 diff --git a/scala/sources/src/test/scala/tests/RunCukesTest.scala b/scala/sources/src/test/scala/tests/cukes/RunCukesTest.scala similarity index 89% rename from scala/sources/src/test/scala/tests/RunCukesTest.scala rename to scala/sources/src/test/scala/tests/cukes/RunCukesTest.scala index 8cf0ea8d..c86c236f 100644 --- a/scala/sources/src/test/scala/tests/RunCukesTest.scala +++ b/scala/sources/src/test/scala/tests/cukes/RunCukesTest.scala @@ -1,4 +1,4 @@ -package tests +package tests.cukes import io.cucumber.junit.{Cucumber, CucumberOptions} import org.junit.runner.RunWith diff --git a/scala/sources/src/test/scala/tests/StepDefs.scala b/scala/sources/src/test/scala/tests/cukes/StepDefs.scala similarity index 98% rename from scala/sources/src/test/scala/tests/StepDefs.scala rename to scala/sources/src/test/scala/tests/cukes/StepDefs.scala index cdc5b75a..180ebc72 100644 --- a/scala/sources/src/test/scala/tests/StepDefs.scala +++ b/scala/sources/src/test/scala/tests/cukes/StepDefs.scala @@ -1,11 +1,11 @@ -package tests +package tests.cukes import java.util.{List => JList, Map => JMap} import io.cucumber.datatable.DataTable import io.cucumber.scala.{EN, ScalaDsl} import junit.framework.Assert._ -import tests.model.{Cukes, Person, Snake} +import tests.cukes.model.{Cukes, Person, Snake} import scala.collection.JavaConverters._ diff --git a/scala/sources/src/test/scala/tests/TypeRegistryConfiguration.scala b/scala/sources/src/test/scala/tests/cukes/TypeRegistryConfiguration.scala similarity index 97% rename from scala/sources/src/test/scala/tests/TypeRegistryConfiguration.scala rename to scala/sources/src/test/scala/tests/cukes/TypeRegistryConfiguration.scala index fa705064..3197c65f 100644 --- a/scala/sources/src/test/scala/tests/TypeRegistryConfiguration.scala +++ b/scala/sources/src/test/scala/tests/cukes/TypeRegistryConfiguration.scala @@ -1,4 +1,4 @@ -package tests +package tests.cukes import java.lang.reflect.Type import java.util @@ -8,7 +8,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import io.cucumber.core.api.{TypeRegistry, TypeRegistryConfigurer} import io.cucumber.cucumberexpressions.{ParameterByTypeTransformer, ParameterType, Transformer} import io.cucumber.datatable.{DataTableType, TableCellByTypeTransformer, TableEntryByTypeTransformer, TableEntryTransformer} -import tests.model.{Cukes, Person, Snake} +import tests.cukes.model.{Cukes, Person, Snake} class TypeRegistryConfiguration extends TypeRegistryConfigurer { diff --git a/scala/sources/src/test/scala/tests/model/Cuke.scala b/scala/sources/src/test/scala/tests/cukes/model/Cuke.scala similarity index 63% rename from scala/sources/src/test/scala/tests/model/Cuke.scala rename to scala/sources/src/test/scala/tests/cukes/model/Cuke.scala index 16db2353..a9a74a08 100644 --- a/scala/sources/src/test/scala/tests/model/Cuke.scala +++ b/scala/sources/src/test/scala/tests/cukes/model/Cuke.scala @@ -1,3 +1,3 @@ -package tests.model +package tests.cukes.model case class Cukes(number: Int, color: String) diff --git a/scala/sources/src/test/scala/tests/model/Person.scala b/scala/sources/src/test/scala/tests/cukes/model/Person.scala similarity index 85% rename from scala/sources/src/test/scala/tests/model/Person.scala rename to scala/sources/src/test/scala/tests/cukes/model/Person.scala index 5a9c4fb5..91ca7eb7 100644 --- a/scala/sources/src/test/scala/tests/model/Person.scala +++ b/scala/sources/src/test/scala/tests/cukes/model/Person.scala @@ -1,4 +1,4 @@ -package tests.model +package tests.cukes.model /** * Test model for a "Person" diff --git a/scala/sources/src/test/scala/tests/model/Snake.scala b/scala/sources/src/test/scala/tests/cukes/model/Snake.scala similarity index 90% rename from scala/sources/src/test/scala/tests/model/Snake.scala rename to scala/sources/src/test/scala/tests/cukes/model/Snake.scala index 59064596..398bf8a2 100644 --- a/scala/sources/src/test/scala/tests/model/Snake.scala +++ b/scala/sources/src/test/scala/tests/cukes/model/Snake.scala @@ -1,4 +1,4 @@ -package tests.model +package tests.cukes.model /** * Test model "Snake" to exercise the custom mapper functionality diff --git a/scala/sources/src/test/scala/tests/isolated/IsolatedSteps.scala b/scala/sources/src/test/scala/tests/isolated/IsolatedSteps.scala new file mode 100644 index 00000000..c64f5d91 --- /dev/null +++ b/scala/sources/src/test/scala/tests/isolated/IsolatedSteps.scala @@ -0,0 +1,26 @@ +package tests.isolated + +import java.util.{List => JList} +import io.cucumber.scala.{EN, ScalaDsl} + +import scala.collection.JavaConverters._ + +class IsolatedSteps extends ScalaDsl with EN { + + var mutableValues: List[Int] = List() + + Given("""I set the list of values to""") { (values: JList[Int]) => + // Obviously this is silly, as we keep the previous value but this is exactly what we want to test + // Isolated scenarios should ensure that the previous value is not kept + mutableValues = mutableValues ++ values.asScala.toList + } + + Given("""I multiply by {int}""") { (mult: Int) => + mutableValues = mutableValues.map(i => i * mult) + } + + Then("""the list of values is""") { (values: JList[Int]) => + assert(mutableValues == values.asScala.toList) + } + +} diff --git a/scala/sources/src/test/scala/tests/isolated/RunIsolatedTest.scala b/scala/sources/src/test/scala/tests/isolated/RunIsolatedTest.scala new file mode 100644 index 00000000..107859d7 --- /dev/null +++ b/scala/sources/src/test/scala/tests/isolated/RunIsolatedTest.scala @@ -0,0 +1,8 @@ +package tests.isolated + +import io.cucumber.junit.{Cucumber, CucumberOptions} +import org.junit.runner.RunWith + +@RunWith(classOf[Cucumber]) +@CucumberOptions(strict = true) +class RunIsolatedTest diff --git a/scala/sources/src/test/scala/tests/object/ObjectSteps.scala b/scala/sources/src/test/scala/tests/object/ObjectSteps.scala new file mode 100644 index 00000000..4d1b33dc --- /dev/null +++ b/scala/sources/src/test/scala/tests/object/ObjectSteps.scala @@ -0,0 +1,27 @@ +package tests.`object` + +import io.cucumber.scala.{EN, ScalaDsl} +import org.junit.Assert.assertEquals + +object ObjectSteps extends ScalaDsl with EN { + + private var calculator: Calculator = _ + private var result: Int = -1 + + Given("""I have a calculator""") { + calculator = new Calculator() + } + + When("""I do {int} + {int}""") { (a: Int, b: Int) => + result = calculator.add(a, b) + } + + Then("""I got {int}""") { (expectedResult: Int) => + assertEquals(expectedResult, result) + } + + private class Calculator { + def add(a: Int, b: Int) = a + b + } + +} diff --git a/scala/sources/src/test/scala/tests/object/RunObjectTest.scala b/scala/sources/src/test/scala/tests/object/RunObjectTest.scala new file mode 100644 index 00000000..90214a42 --- /dev/null +++ b/scala/sources/src/test/scala/tests/object/RunObjectTest.scala @@ -0,0 +1,8 @@ +package tests.`object` + +import io.cucumber.junit.{Cucumber, CucumberOptions} +import org.junit.runner.RunWith + +@RunWith(classOf[Cucumber]) +@CucumberOptions(strict = true) +class RunObjectTest From 46c22fd828da446d6c024d9b2d7ee39f2d126d03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Jourdan-Weil?= Date: Sun, 26 Apr 2020 14:49:38 +0200 Subject: [PATCH 3/6] Implement scenario scoped glue code for classes --- .../scala/AbstractGlueDefinition.scala | 25 +- .../scala/io/cucumber/scala/GlueAdaptor.scala | 18 +- .../io/cucumber/scala/ScalaBackend.scala | 37 +- .../scala/ScalaBackendProviderService.scala | 2 +- .../scala/io/cucumber/scala/ScalaDsl.scala | 56 +- .../io/cucumber/scala/ScalaDslRegistry.scala | 12 +- .../cucumber/scala/ScalaHookDefinition.scala | 35 +- .../io/cucumber/scala/ScalaHookDetails.scala | 5 + .../cucumber/scala/ScalaStepDefinition.scala | 67 +- .../io/cucumber/scala/ScalaStepDetails.scala | 19 + .../io/cucumber/scala/ScalaBackendTest.scala | 147 +++- .../io/cucumber/scala/ScalaDslTest.scala | 630 +++++++++++++++--- .../scala/ScalaHookDefinitionTest.scala | 19 - .../scala/ScalaStepDefinitionTest.scala | 46 -- .../scala/io/cucumber/scala/steps/Steps.scala | 11 - .../steps/classes/MultipleInSameFile.scala | 35 + .../scala/steps/classes/SingleFile.scala | 11 + .../scala/steps/objects/StepsInObject.scala | 27 + .../scala/steps/traits/StepsInTrait.scala | 31 + 19 files changed, 950 insertions(+), 283 deletions(-) create mode 100644 scala/sources/src/main/scala/io/cucumber/scala/ScalaHookDetails.scala create mode 100644 scala/sources/src/main/scala/io/cucumber/scala/ScalaStepDetails.scala delete mode 100644 scala/sources/src/test/scala/io/cucumber/scala/ScalaHookDefinitionTest.scala delete mode 100644 scala/sources/src/test/scala/io/cucumber/scala/ScalaStepDefinitionTest.scala delete mode 100644 scala/sources/src/test/scala/io/cucumber/scala/steps/Steps.scala create mode 100644 scala/sources/src/test/scala/io/cucumber/scala/steps/classes/MultipleInSameFile.scala create mode 100644 scala/sources/src/test/scala/io/cucumber/scala/steps/classes/SingleFile.scala create mode 100644 scala/sources/src/test/scala/io/cucumber/scala/steps/objects/StepsInObject.scala create mode 100644 scala/sources/src/test/scala/io/cucumber/scala/steps/traits/StepsInTrait.scala diff --git a/scala/sources/src/main/scala/io/cucumber/scala/AbstractGlueDefinition.scala b/scala/sources/src/main/scala/io/cucumber/scala/AbstractGlueDefinition.scala index 9813d39e..853d2bef 100644 --- a/scala/sources/src/main/scala/io/cucumber/scala/AbstractGlueDefinition.scala +++ b/scala/sources/src/main/scala/io/cucumber/scala/AbstractGlueDefinition.scala @@ -1,15 +1,32 @@ package io.cucumber.scala -import io.cucumber.core.backend.Located +import java.lang.reflect.InvocationTargetException -abstract class AbstractGlueDefinition(location: StackTraceElement) extends Located { +import io.cucumber.core.backend.{CucumberInvocationTargetException, Located} - def getLocation(): String = { +import scala.util.{Failure, Try} + +trait AbstractGlueDefinition extends Located { + + val location: StackTraceElement + + override def getLocation(): String = { location.toString } - def isDefinedAt(stackTraceElement: StackTraceElement): Boolean = { + override def isDefinedAt(stackTraceElement: StackTraceElement): Boolean = { location.getFileName != null && location.getFileName == stackTraceElement.getFileName } + /** + * Executes the block of code and handle failures in the way asked by Cucumber specification: that is throwing a CucumberInvocationTargetException. + */ + protected def executeAsCucumber(block: => Unit): Unit = { + Try(block) + .recoverWith { + case ex => Failure(new CucumberInvocationTargetException(this, new InvocationTargetException(ex))) + } + .get + } + } diff --git a/scala/sources/src/main/scala/io/cucumber/scala/GlueAdaptor.scala b/scala/sources/src/main/scala/io/cucumber/scala/GlueAdaptor.scala index 8d910885..1d2cb7ea 100644 --- a/scala/sources/src/main/scala/io/cucumber/scala/GlueAdaptor.scala +++ b/scala/sources/src/main/scala/io/cucumber/scala/GlueAdaptor.scala @@ -4,12 +4,18 @@ 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) + /** + * Load the step definitions and hooks from a ScalaDsl instance into the glue. + * + * @param registry ScalaDsl instance registry + * @param scenarioScoped true for class instances, false for object singletons + */ + def loadRegistry(registry: ScalaDslRegistry, scenarioScoped: Boolean): Unit = { + registry.stepDefinitions.map(ScalaStepDefinition(_, scenarioScoped)).foreach(glue.addStepDefinition) + registry.beforeHooks.map(ScalaHookDefinition(_, scenarioScoped)).foreach(glue.addBeforeHook) + registry.afterHooks.map(ScalaHookDefinition(_, scenarioScoped)).foreach(glue.addAfterHook) + registry.afterStepHooks.map(ScalaHookDefinition(_, scenarioScoped)).foreach(glue.addAfterStepHook) + registry.beforeStepHooks.map(ScalaHookDefinition(_, scenarioScoped)).foreach(glue.addBeforeStepHook) } } diff --git a/scala/sources/src/main/scala/io/cucumber/scala/ScalaBackend.scala b/scala/sources/src/main/scala/io/cucumber/scala/ScalaBackend.scala index ae943dae..1e3c66a6 100644 --- a/scala/sources/src/main/scala/io/cucumber/scala/ScalaBackend.scala +++ b/scala/sources/src/main/scala/io/cucumber/scala/ScalaBackend.scala @@ -11,14 +11,15 @@ import io.cucumber.core.resource.{ClasspathScanner, ClasspathSupport} import scala.collection.JavaConverters._ import scala.util.Try -class ScalaBackend(classLoaderProvider: Supplier[ClassLoader]) extends Backend { +class ScalaBackend(lookup: Lookup, container: Container, classLoaderProvider: Supplier[ClassLoader]) extends Backend { private val classFinder = new ClasspathScanner(classLoaderProvider) - private[scala] var scalaGlueInstances: Seq[ScalaDsl] = Nil + private var glueAdaptor: GlueAdaptor = _ + private[scala] var scalaGlueClasses: Seq[Class[_ <: ScalaDsl]] = Nil override def disposeWorld(): Unit = { - scalaGlueInstances = Nil + // Nothing to do } override def getSnippet(): Snippet = { @@ -26,38 +27,42 @@ class ScalaBackend(classLoaderProvider: Supplier[ClassLoader]) extends Backend { } override def buildWorld(): Unit = { - // Nothing to do + // Instantiate all the glue classes and load the glue code from them + scalaGlueClasses.foreach { glueClass => + val glueInstance = lookup.getInstance(glueClass) + glueAdaptor.loadRegistry(glueInstance.registry, scenarioScoped = true) + } } override def loadGlue(glue: Glue, gluePaths: JList[URI]): Unit = { + glueAdaptor = new GlueAdaptor(glue) + 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) + // Retrieve Scala objects (singletons) 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) + // Regular Scala classes are added to the container, they will be instantiated by the container depending on its logic + // Object are not because by definition they are singletons + clsClasses.foreach { glueClass => + container.addClass(glueClass) + scalaGlueClasses = scalaGlueClasses :+ glueClass + } - scalaGlueInstances.foreach { glueInstance => - glueAdaptor.addDefinition(glueInstance.registry) + // For object, we add the definitions here, once for all + objInstances.foreach { glueInstance => + glueAdaptor.loadRegistry(glueInstance.registry, scenarioScoped = 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 index 0a3fdec3..7c91b5cf 100644 --- a/scala/sources/src/main/scala/io/cucumber/scala/ScalaBackendProviderService.scala +++ b/scala/sources/src/main/scala/io/cucumber/scala/ScalaBackendProviderService.scala @@ -7,7 +7,7 @@ import io.cucumber.core.backend.{Backend, BackendProviderService, Container, Loo class ScalaBackendProviderService extends BackendProviderService { override def create(lookup: Lookup, container: Container, classLoaderSupplier: Supplier[ClassLoader]): Backend = { - new ScalaBackend(classLoaderSupplier) + new ScalaBackend(lookup, container, classLoaderSupplier) } } diff --git a/scala/sources/src/main/scala/io/cucumber/scala/ScalaDsl.scala b/scala/sources/src/main/scala/io/cucumber/scala/ScalaDsl.scala index b6ba62c3..684df298 100644 --- a/scala/sources/src/main/scala/io/cucumber/scala/ScalaDsl.scala +++ b/scala/sources/src/main/scala/io/cucumber/scala/ScalaDsl.scala @@ -19,7 +19,7 @@ trait ScalaDsl { import scala.language.implicitConversions - val registry = new ScalaDslRegistry() + private[scala] val registry: ScalaDslRegistry = new ScalaDslRegistry() // TODO support Before/After with no parameter @@ -36,7 +36,7 @@ trait ScalaDsl { } def Before(tagExpression: String, order: Int)(body: HookBody): Unit = { - registry.beforeHooks += new ScalaHookDefinition(tagExpression, order, body) + registry.beforeHooks += ScalaHookDetails(tagExpression, order, body) } def BeforeStep(body: HookBody): Unit = { @@ -52,7 +52,7 @@ trait ScalaDsl { } def BeforeStep(tagExpression: String, order: Int)(body: HookBody): Unit = { - registry.beforeStepHooks += new ScalaHookDefinition(tagExpression, order, body) + registry.beforeStepHooks += ScalaHookDetails(tagExpression, order, body) } def After(body: HookBody): Unit = { @@ -68,7 +68,7 @@ trait ScalaDsl { } def After(tagExpression: String, order: Int)(body: HookBody): Unit = { - registry.afterHooks += new ScalaHookDefinition(tagExpression, order, body) + registry.afterHooks += ScalaHookDetails(tagExpression, order, body) } def AfterStep(body: HookBody): Unit = { @@ -84,7 +84,7 @@ trait ScalaDsl { } def AfterStep(tagExpression: String, order: Int)(body: HookBody): Unit = { - registry.afterStepHooks += new ScalaHookDefinition(tagExpression, order, body) + registry.afterStepHooks += ScalaHookDetails(tagExpression, order, body) } @@ -135,14 +135,14 @@ trait ScalaDsl { /* * Generated apply1 to apply22 below */ - def apply[T1](f: (T1) => Any)(implicit m1: Manifest[T1]) = { + def apply[T1](f: T1 => Any)(implicit m1: Manifest[T1]): Unit = { register(m1) { case List(a1: AnyRef) => f(a1.asInstanceOf[T1]) } } - def apply[T1, T2](f: (T1, T2) => Any)(implicit m1: Manifest[T1], m2: Manifest[T2]) = { + def apply[T1, T2](f: (T1, T2) => Any)(implicit m1: Manifest[T1], m2: Manifest[T2]): Unit = { register(m1, m2) { case List(a1: AnyRef, a2: AnyRef) => f(a1.asInstanceOf[T1], @@ -150,7 +150,7 @@ trait ScalaDsl { } } - def apply[T1, T2, T3](f: (T1, T2, T3) => Any)(implicit m1: Manifest[T1], m2: Manifest[T2], m3: Manifest[T3]) = { + def apply[T1, T2, T3](f: (T1, T2, T3) => Any)(implicit m1: Manifest[T1], m2: Manifest[T2], m3: Manifest[T3]): Unit = { register(m1, m2, m3) { case List(a1: AnyRef, a2: AnyRef, a3: AnyRef) => f(a1.asInstanceOf[T1], @@ -159,7 +159,7 @@ trait ScalaDsl { } } - def apply[T1, T2, T3, T4](f: (T1, T2, T3, T4) => Any)(implicit m1: Manifest[T1], m2: Manifest[T2], m3: Manifest[T3], m4: Manifest[T4]) = { + def apply[T1, T2, T3, T4](f: (T1, T2, T3, T4) => Any)(implicit m1: Manifest[T1], m2: Manifest[T2], m3: Manifest[T3], m4: Manifest[T4]): Unit = { register(m1, m2, m3, m4) { case List(a1: AnyRef, a2: AnyRef, a3: AnyRef, a4: AnyRef) => f(a1.asInstanceOf[T1], @@ -169,7 +169,7 @@ trait ScalaDsl { } } - def apply[T1, T2, T3, T4, T5](f: (T1, T2, T3, T4, T5) => Any)(implicit m1: Manifest[T1], m2: Manifest[T2], m3: Manifest[T3], m4: Manifest[T4], m5: Manifest[T5]) = { + def apply[T1, T2, T3, T4, T5](f: (T1, T2, T3, T4, T5) => Any)(implicit m1: Manifest[T1], m2: Manifest[T2], m3: Manifest[T3], m4: Manifest[T4], m5: Manifest[T5]): Unit = { register(m1, m2, m3, m4, m5) { case List(a1: AnyRef, a2: AnyRef, a3: AnyRef, a4: AnyRef, a5: AnyRef) => f(a1.asInstanceOf[T1], @@ -180,7 +180,7 @@ trait ScalaDsl { } } - def apply[T1, T2, T3, T4, T5, T6](f: (T1, T2, T3, T4, T5, T6) => Any)(implicit m1: Manifest[T1], m2: Manifest[T2], m3: Manifest[T3], m4: Manifest[T4], m5: Manifest[T5], m6: Manifest[T6]) = { + def apply[T1, T2, T3, T4, T5, T6](f: (T1, T2, T3, T4, T5, T6) => Any)(implicit m1: Manifest[T1], m2: Manifest[T2], m3: Manifest[T3], m4: Manifest[T4], m5: Manifest[T5], m6: Manifest[T6]): Unit = { register(m1, m2, m3, m4, m5, m6) { case List(a1: AnyRef, a2: AnyRef, a3: AnyRef, a4: AnyRef, a5: AnyRef, a6: AnyRef) => f(a1.asInstanceOf[T1], @@ -192,7 +192,7 @@ trait ScalaDsl { } } - def apply[T1, T2, T3, T4, T5, T6, T7](f: (T1, T2, T3, T4, T5, T6, T7) => Any)(implicit m1: Manifest[T1], m2: Manifest[T2], m3: Manifest[T3], m4: Manifest[T4], m5: Manifest[T5], m6: Manifest[T6], m7: Manifest[T7]) = { + def apply[T1, T2, T3, T4, T5, T6, T7](f: (T1, T2, T3, T4, T5, T6, T7) => Any)(implicit m1: Manifest[T1], m2: Manifest[T2], m3: Manifest[T3], m4: Manifest[T4], m5: Manifest[T5], m6: Manifest[T6], m7: Manifest[T7]): Unit = { register(m1, m2, m3, m4, m5, m6, m7) { case List(a1: AnyRef, a2: AnyRef, a3: AnyRef, a4: AnyRef, a5: AnyRef, a6: AnyRef, a7: AnyRef) => f(a1.asInstanceOf[T1], @@ -205,7 +205,7 @@ trait ScalaDsl { } } - def apply[T1, T2, T3, T4, T5, T6, T7, T8](f: (T1, T2, T3, T4, T5, T6, T7, T8) => 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]) = { + def apply[T1, T2, T3, T4, T5, T6, T7, T8](f: (T1, T2, T3, T4, T5, T6, T7, T8) => 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]): Unit = { register(m1, m2, m3, m4, m5, m6, m7, m8) { case List(a1: AnyRef, a2: AnyRef, a3: AnyRef, a4: AnyRef, a5: AnyRef, a6: AnyRef, a7: AnyRef, a8: AnyRef) => f(a1.asInstanceOf[T1], @@ -219,7 +219,7 @@ trait ScalaDsl { } } - def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9](f: (T1, T2, T3, T4, T5, T6, T7, T8, T9) => 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]) = { + def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9](f: (T1, T2, T3, T4, T5, T6, T7, T8, T9) => 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]): Unit = { register(m1, m2, m3, m4, m5, m6, m7, m8, m9) { case List(a1: AnyRef, a2: AnyRef, a3: AnyRef, a4: AnyRef, a5: AnyRef, a6: AnyRef, a7: AnyRef, a8: AnyRef, a9: AnyRef) => f(a1.asInstanceOf[T1], @@ -234,7 +234,7 @@ trait ScalaDsl { } } - def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10](f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) => 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]) = { + def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10](f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) => 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]): Unit = { register(m1, m2, m3, m4, m5, m6, m7, m8, m9, m10) { case List(a1: AnyRef, a2: AnyRef, a3: AnyRef, a4: AnyRef, a5: AnyRef, a6: AnyRef, a7: AnyRef, a8: AnyRef, a9: AnyRef, a10: AnyRef) => f(a1.asInstanceOf[T1], @@ -250,7 +250,7 @@ trait ScalaDsl { } } - def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11](f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) => 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]) = { + def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11](f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) => 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]): Unit = { register(m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11) { case List(a1: AnyRef, a2: AnyRef, a3: AnyRef, a4: AnyRef, a5: AnyRef, a6: AnyRef, a7: AnyRef, a8: AnyRef, a9: AnyRef, a10: AnyRef, a11: AnyRef) => f(a1.asInstanceOf[T1], @@ -267,7 +267,7 @@ trait ScalaDsl { } } - def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12](f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) => 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]) = { + def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12](f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) => 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]): Unit = { register(m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12) { 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) => f(a1.asInstanceOf[T1], @@ -285,7 +285,7 @@ trait ScalaDsl { } } - def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13](f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) => 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]) = { + def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13](f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) => 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]): Unit = { register(m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13) { 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) => f(a1.asInstanceOf[T1], @@ -304,7 +304,7 @@ trait ScalaDsl { } } - def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14](f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) => 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]) = { + def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14](f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) => 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]): Unit = { register(m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14) { 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) => f(a1.asInstanceOf[T1], @@ -324,7 +324,7 @@ trait ScalaDsl { } } - def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15](f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15) => 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]) = { + def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15](f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15) => 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]): Unit = { register(m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15) { 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) => f(a1.asInstanceOf[T1], @@ -345,7 +345,7 @@ 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]) = { + 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]): Unit = { 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], @@ -367,7 +367,7 @@ trait ScalaDsl { } } - def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17](f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17) => 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]) = { + def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17](f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17) => 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]): Unit = { register(m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17) { 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) => f(a1.asInstanceOf[T1], @@ -390,7 +390,7 @@ trait ScalaDsl { } } - def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18](f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18) => 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]) = { + def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18](f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18) => 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]): Unit = { register(m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18) { 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) => f(a1.asInstanceOf[T1], @@ -414,7 +414,7 @@ trait ScalaDsl { } } - def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19](f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19) => 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]) = { + def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19](f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19) => 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]): Unit = { register(m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19) { 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) => f(a1.asInstanceOf[T1], @@ -439,7 +439,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](f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20) => 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]) = { + def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20](f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20) => 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]): Unit = { register(m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20) { 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) => f(a1.asInstanceOf[T1], @@ -465,7 +465,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](f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21) => 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]) = { + def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21](f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21) => 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]): Unit = { register(m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20, m21) { 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) => f(a1.asInstanceOf[T1], @@ -492,7 +492,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]) = { + 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]): Unit = { 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], @@ -522,7 +522,7 @@ trait ScalaDsl { private def register(ms: Manifest[_ <: Any]*)(pf: PartialFunction[List[Any], Any]): Unit = { val types = ms.map(m => toJavaType(m)).toArray - registry.stepDefinitions += new ScalaStepDefinition(Utils.frame(self), name, regex, types, pf) + registry.stepDefinitions += ScalaStepDetails(Utils.frame(self), name, regex, types, pf) } private def toJavaType(m: Manifest[_]): Type = { diff --git a/scala/sources/src/main/scala/io/cucumber/scala/ScalaDslRegistry.scala b/scala/sources/src/main/scala/io/cucumber/scala/ScalaDslRegistry.scala index 331e5b93..fa2daf9d 100644 --- a/scala/sources/src/main/scala/io/cucumber/scala/ScalaDslRegistry.scala +++ b/scala/sources/src/main/scala/io/cucumber/scala/ScalaDslRegistry.scala @@ -1,20 +1,18 @@ package io.cucumber.scala -import io.cucumber.core.backend.{HookDefinition, StepDefinition} - import scala.collection.mutable.ArrayBuffer class ScalaDslRegistry { - val stepDefinitions = new ArrayBuffer[StepDefinition] + val stepDefinitions = new ArrayBuffer[ScalaStepDetails] - val beforeHooks = new ArrayBuffer[HookDefinition] + val beforeHooks = new ArrayBuffer[ScalaHookDetails] - val beforeStepHooks = new ArrayBuffer[HookDefinition] + val beforeStepHooks = new ArrayBuffer[ScalaHookDetails] - val afterHooks = new ArrayBuffer[HookDefinition] + val afterHooks = new ArrayBuffer[ScalaHookDetails] - val afterStepHooks = new ArrayBuffer[HookDefinition] + val afterStepHooks = new ArrayBuffer[ScalaHookDetails] } diff --git a/scala/sources/src/main/scala/io/cucumber/scala/ScalaHookDefinition.scala b/scala/sources/src/main/scala/io/cucumber/scala/ScalaHookDefinition.scala index 0b8208fb..8b79be97 100644 --- a/scala/sources/src/main/scala/io/cucumber/scala/ScalaHookDefinition.scala +++ b/scala/sources/src/main/scala/io/cucumber/scala/ScalaHookDefinition.scala @@ -1,18 +1,37 @@ package io.cucumber.scala -import io.cucumber.core.backend.{HookDefinition, TestCaseState} -import io.cucumber.scala.Aliases.HookBody +import io.cucumber.core.backend.{HookDefinition, ScenarioScoped, TestCaseState} -class ScalaHookDefinition(tagExpression: String, order: Int, body: HookBody) - extends AbstractGlueDefinition(new Exception().getStackTrace()(3)) - with HookDefinition { +trait ScalaHookDefinition extends HookDefinition with AbstractGlueDefinition { + + val hookDetails: ScalaHookDetails + + override val location: StackTraceElement = new Exception().getStackTrace()(3) override def execute(state: TestCaseState): Unit = { - body(new Scenario(state)) + executeAsCucumber(hookDetails.body(new Scenario(state))) } - override def getTagExpression: String = tagExpression + override def getTagExpression: String = hookDetails.tagExpression + + override def getOrder: Int = hookDetails.order + +} + +object ScalaHookDefinition { - override def getOrder: Int = order + def apply(scalaHookDetails: ScalaHookDetails, scenarioScoped: Boolean): ScalaHookDefinition = { + if (scenarioScoped) { + new ScalaScenarioScopedHookDefinition(scalaHookDetails) + } else { + new ScalaGlobalHookDefinition(scalaHookDetails) + } + } + +} + +class ScalaScenarioScopedHookDefinition(override val hookDetails: ScalaHookDetails) extends ScalaHookDefinition with ScenarioScoped { +} +class ScalaGlobalHookDefinition(override val hookDetails: ScalaHookDetails) extends ScalaHookDefinition { } diff --git a/scala/sources/src/main/scala/io/cucumber/scala/ScalaHookDetails.scala b/scala/sources/src/main/scala/io/cucumber/scala/ScalaHookDetails.scala new file mode 100644 index 00000000..fb00604b --- /dev/null +++ b/scala/sources/src/main/scala/io/cucumber/scala/ScalaHookDetails.scala @@ -0,0 +1,5 @@ +package io.cucumber.scala + +import io.cucumber.scala.Aliases.HookBody + +case class ScalaHookDetails(tagExpression: String, order: Int, body: HookBody) diff --git a/scala/sources/src/main/scala/io/cucumber/scala/ScalaStepDefinition.scala b/scala/sources/src/main/scala/io/cucumber/scala/ScalaStepDefinition.scala index ef3d7552..921465e6 100644 --- a/scala/sources/src/main/scala/io/cucumber/scala/ScalaStepDefinition.scala +++ b/scala/sources/src/main/scala/io/cucumber/scala/ScalaStepDefinition.scala @@ -1,32 +1,20 @@ package io.cucumber.scala -import java.lang.reflect.{InvocationTargetException, Type} +import java.lang.reflect.Type import java.util.{List => JList} -import io.cucumber.core.backend.{CucumberInvocationTargetException, ParameterInfo, StepDefinition} +import io.cucumber.core.backend.{ParameterInfo, ScenarioScoped, StepDefinition} import scala.collection.JavaConverters._ -import scala.util.{Failure, Try} - -/** - * 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 types Parameters types of body step definition - * @param body 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, - types: Array[Type], - body: List[Any] => Any) - extends AbstractGlueDefinition(frame) - with StepDefinition { - - val parametersInfo: JList[ParameterInfo] = fromTypes(types) + + +trait ScalaStepDefinition extends StepDefinition with AbstractGlueDefinition { + + val stepDetails: ScalaStepDetails + + override val location: StackTraceElement = stepDetails.frame + + val parametersInfo: JList[ParameterInfo] = fromTypes(stepDetails.types) private def fromTypes(types: Array[Type]): JList[ParameterInfo] = { types @@ -38,23 +26,32 @@ class ScalaStepDefinition(frame: StackTraceElement, } override def execute(args: Array[AnyRef]): Unit = { - Try { - body(args.toList) - } - // We do this to respect the contract defined by StepDefinition - // although throwing exceptions is not very Scala-ish - .recoverWith { - case ex => Failure(new CucumberInvocationTargetException(this, new InvocationTargetException(ex))) - } - .get - + executeAsCucumber(stepDetails.body(args.toList)) } - override def getPattern: String = pattern + override def getPattern: String = stepDetails.pattern override def parameterInfos(): JList[ParameterInfo] = parametersInfo // Easier to just print out fileName and lineNumber - override def getLocation(): String = frame.getFileName + ":" + frame.getLineNumber + override def getLocation(): String = stepDetails.frame.getFileName + ":" + stepDetails.frame.getLineNumber + +} + +object ScalaStepDefinition { + + def apply(stepDetails: ScalaStepDetails, scenarioScoped: Boolean): ScalaStepDefinition = { + if (scenarioScoped) { + new ScalaScenarioScopedStepDefinition(stepDetails) + } else { + new ScalaGlobalStepDefinition(stepDetails) + } + } + +} + +class ScalaScenarioScopedStepDefinition(override val stepDetails: ScalaStepDetails) extends ScalaStepDefinition with ScenarioScoped { +} +class ScalaGlobalStepDefinition(override val stepDetails: ScalaStepDetails) extends ScalaStepDefinition { } diff --git a/scala/sources/src/main/scala/io/cucumber/scala/ScalaStepDetails.scala b/scala/sources/src/main/scala/io/cucumber/scala/ScalaStepDetails.scala new file mode 100644 index 00000000..e40ef3db --- /dev/null +++ b/scala/sources/src/main/scala/io/cucumber/scala/ScalaStepDetails.scala @@ -0,0 +1,19 @@ +package io.cucumber.scala + +import java.lang.reflect.Type + +/** + * 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 types Parameters types of body step definition + * @param body Function body of a step definition. This is what actually runs the code within the step def. + */ +case class ScalaStepDetails(frame: StackTraceElement, + name: String, + pattern: String, + types: Array[Type], + body: List[Any] => Any) \ No newline at end of file diff --git a/scala/sources/src/test/scala/io/cucumber/scala/ScalaBackendTest.scala b/scala/sources/src/test/scala/io/cucumber/scala/ScalaBackendTest.scala index 190d2d98..ac31346c 100644 --- a/scala/sources/src/test/scala/io/cucumber/scala/ScalaBackendTest.scala +++ b/scala/sources/src/test/scala/io/cucumber/scala/ScalaBackendTest.scala @@ -3,35 +3,156 @@ package io.cucumber.scala import java.net.URI import java.util.function.Supplier -import io.cucumber.core.backend.Glue +import io.cucumber.core.backend._ +import io.cucumber.scala.steps.classes.{StepsA, StepsB, StepsC} +import io.cucumber.scala.steps.traits.StepsInTrait import org.junit.Assert.{assertEquals, assertTrue} import org.junit.{Before, Test} -import org.mockito.Mockito.mock +import org.mockito.ArgumentMatchers.any +import org.mockito.Mockito._ import scala.collection.JavaConverters._ class ScalaBackendTest { - private val glue: Glue = mock(classOf[Glue]) + private val fakeGlue: Glue = mock(classOf[Glue]) + private val fakeLookup: Lookup = mock(classOf[Lookup]) + private val fakeContainer: Container = mock(classOf[Container]) + + // Note: keep unnecessary "new" for Scala 2.11 compat + private val classLoaderSupplier: Supplier[ClassLoader] = new Supplier[ClassLoader] { + override def get(): ClassLoader = Thread.currentThread().getContextClassLoader + } private var backend: ScalaBackend = _ @Before - def createBackend() = { - // Note: keep unnecessary code for Scala 2.11 compat - val classLoaderSupplier: Supplier[ClassLoader] = new Supplier[ClassLoader] { - override def get(): ClassLoader = Thread.currentThread().getContextClassLoader - } - backend = new ScalaBackend(classLoaderSupplier) + def beforeEach(): Unit = { + // Reset mocks + reset(fakeGlue, fakeLookup, fakeContainer) + + // Mock the Lookup + when(fakeLookup.getInstance(classOf[StepsA])).thenReturn(new StepsA()) + when(fakeLookup.getInstance(classOf[StepsB])).thenReturn(new StepsB()) + when(fakeLookup.getInstance(classOf[StepsC])).thenReturn(new StepsC()) + when(fakeLookup.getInstance(classOf[StepsInTrait])).thenReturn(new StepsInTrait()) + + // Create the instances + backend = new ScalaBackend(fakeLookup, fakeContainer, classLoaderSupplier) + } + + @Test + def loadGlueAndBuildWorld_classes(): Unit = { + // Load glue + backend.loadGlue(fakeGlue, List(URI.create("classpath:io/cucumber/scala/steps/classes")).asJava) + + assertEquals(3, backend.scalaGlueClasses.size) + assertTrue(backend.scalaGlueClasses.toSet == Set(classOf[StepsA], classOf[StepsB], classOf[StepsC])) + + verify(fakeContainer, times(3)).addClass(any()) + verify(fakeContainer, times(1)).addClass(classOf[StepsA]) + verify(fakeContainer, times(1)).addClass(classOf[StepsB]) + verify(fakeContainer, times(1)).addClass(classOf[StepsC]) + + verify(fakeGlue, never()).addStepDefinition(any()) + verify(fakeGlue, never()).addBeforeHook(any()) + verify(fakeGlue, never()).addBeforeStepHook(any()) + verify(fakeGlue, never()).addAfterHook(any()) + verify(fakeGlue, never()).addAfterStepHook(any()) + + // Build world + backend.buildWorld() + + verify(fakeLookup, times(3)).getInstance(any()) + verify(fakeLookup, times(1)).getInstance(classOf[StepsA]) + verify(fakeLookup, times(1)).getInstance(classOf[StepsB]) + verify(fakeLookup, times(1)).getInstance(classOf[StepsC]) + + verify(fakeGlue, times(3)).addStepDefinition(any()) + verify(fakeGlue, times(1)).addBeforeHook(any()) + verify(fakeGlue, times(1)).addBeforeStepHook(any()) + verify(fakeGlue, times(1)).addAfterHook(any()) + verify(fakeGlue, times(1)).addAfterStepHook(any()) + + // Building the world a second time should create new instances + backend.disposeWorld() + backend.buildWorld() + + verify(fakeLookup, times(6)).getInstance(any()) + verify(fakeLookup, times(2)).getInstance(classOf[StepsA]) + verify(fakeLookup, times(2)).getInstance(classOf[StepsB]) + verify(fakeLookup, times(2)).getInstance(classOf[StepsC]) + + verify(fakeGlue, times(6)).addStepDefinition(any()) + verify(fakeGlue, times(2)).addBeforeHook(any()) + verify(fakeGlue, times(2)).addBeforeStepHook(any()) + verify(fakeGlue, times(2)).addAfterHook(any()) + verify(fakeGlue, times(2)).addAfterStepHook(any()) } @Test - def finds_step_definitions_by_classpath_url() = { - backend.loadGlue(glue, List(URI.create("classpath:io/cucumber/scala/steps")).asJava) + def loadGlueAndBuildWorld_trait(): Unit = { + // Load glue + backend.loadGlue(fakeGlue, List(URI.create("classpath:io/cucumber/scala/steps/traits")).asJava) + + assertEquals(1, backend.scalaGlueClasses.size) + assertTrue(backend.scalaGlueClasses.toSet == Set(classOf[StepsInTrait])) + + verify(fakeContainer, times(1)).addClass(any()) + verify(fakeContainer, times(1)).addClass(classOf[StepsInTrait]) + + verify(fakeGlue, never()).addStepDefinition(any()) + verify(fakeGlue, never()).addBeforeHook(any()) + verify(fakeGlue, never()).addBeforeStepHook(any()) + verify(fakeGlue, never()).addAfterHook(any()) + verify(fakeGlue, never()).addAfterStepHook(any()) + + // Build world + backend.buildWorld() + + verify(fakeLookup, times(1)).getInstance(any()) + verify(fakeLookup, times(1)).getInstance(classOf[StepsInTrait]) + + verify(fakeGlue, times(1)).addStepDefinition(any()) + verify(fakeGlue, times(1)).addBeforeHook(any()) + verify(fakeGlue, times(1)).addBeforeStepHook(any()) + verify(fakeGlue, times(1)).addAfterHook(any()) + verify(fakeGlue, times(1)).addAfterStepHook(any()) + + // Building the world a second time should create new instances + backend.disposeWorld() + backend.buildWorld() + + verify(fakeLookup, times(2)).getInstance(any()) + verify(fakeLookup, times(2)).getInstance(classOf[StepsInTrait]) + + verify(fakeGlue, times(2)).addStepDefinition(any()) + verify(fakeGlue, times(2)).addBeforeHook(any()) + verify(fakeGlue, times(2)).addBeforeStepHook(any()) + verify(fakeGlue, times(2)).addAfterHook(any()) + verify(fakeGlue, times(2)).addAfterStepHook(any()) + } + + @Test + def loadGlueAndBuildWorld_object(): Unit = { + // Load glue + backend.loadGlue(fakeGlue, List(URI.create("classpath:io/cucumber/scala/steps/objects")).asJava) + + assertEquals(0, backend.scalaGlueClasses.size) + assertTrue(backend.scalaGlueClasses.toSet == Set()) + + verify(fakeContainer, never()).addClass(any()) + + verify(fakeGlue, times(1)).addStepDefinition(any()) + verify(fakeGlue, times(1)).addBeforeHook(any()) + verify(fakeGlue, times(1)).addBeforeStepHook(any()) + verify(fakeGlue, times(1)).addAfterHook(any()) + verify(fakeGlue, times(1)).addAfterStepHook(any()) + + // Build world backend.buildWorld() - assertEquals(1, backend.scalaGlueInstances.size) - assertTrue(backend.scalaGlueInstances.head.isInstanceOf[io.cucumber.scala.steps.Steps]) + verify(fakeLookup, never()).getInstance(any()) } } diff --git a/scala/sources/src/test/scala/io/cucumber/scala/ScalaDslTest.scala b/scala/sources/src/test/scala/io/cucumber/scala/ScalaDslTest.scala index 5cae2a92..5bad4f99 100644 --- a/scala/sources/src/test/scala/io/cucumber/scala/ScalaDslTest.scala +++ b/scala/sources/src/test/scala/io/cucumber/scala/ScalaDslTest.scala @@ -1,181 +1,633 @@ package io.cucumber.scala -import java.net.URI +import java.util.concurrent.atomic.AtomicBoolean -import io.cucumber.core.backend.TestCaseState +import io.cucumber.core.backend.{CucumberInvocationTargetException, HookDefinition, StepDefinition, TestCaseState} import org.junit.Assert.{assertEquals, assertTrue} -import org.junit.Test +import org.junit.{Before, Test} +import org.mockito.Mockito.mock + +import scala.util.Try class ScalaDslTest { - val testCaseTest: TestCaseState = new TestCaseState { - override def getSourceTagNames = null + private val fakeState = mock(classOf[TestCaseState]) - override def getStatus = null + private val invoked = new AtomicBoolean() - override def isFailed = false + private def invoke(): Unit = { + invoked.set(true) + } - override def embed(p1: Array[Byte], p2: String) {} + @Before + def beforeEach(): Unit = { + // Reset the invoked flag + invoked.set(false) + } - override def embed(p1: Array[Byte], p2: String, p3: String) {} + @Test + def testBeforeHook(): Unit = { - override def write(p1: String) {} + class Glue extends ScalaDsl { + Before { _ => + invoke() + } + } - override def getName = "" + val glue = new Glue() - override def getId = "" + assertClassHook(glue.registry.beforeHooks.head, "", 1000) + } - override def getUri = new URI("classpath:plop") + @Test + def testBeforeHookWithTag(): Unit = { - override def getLine: Integer = null + class Glue extends ScalaDsl { + Before("tagExpression") { _ => + invoke() + } + } + + val glue = new Glue() + + assertClassHook(glue.registry.beforeHooks.head, "tagExpression", 1000) } @Test - def emptyBefore { + def testBeforeHookWithOrder(): Unit = { + + class Glue extends ScalaDsl { + Before(42) { _ => + invoke() + } + } - var actualScenario: Scenario = null + val glue = new Glue() + + assertClassHook(glue.registry.beforeHooks.head, "", 42) + } - object Befores extends ScalaDsl with EN { - Before { - actualScenario = _ + @Test + def testBeforeHookWithTagAndOrder(): Unit = { + + class Glue extends ScalaDsl { + Before("tagExpression", 42) { _ => + invoke() } } - assertEquals(1, Befores.registry.beforeHooks.size) + val glue = new Glue() + + assertClassHook(glue.registry.beforeHooks.head, "tagExpression", 42) + } + + @Test + def testBeforeStepHook(): Unit = { + + class Glue extends ScalaDsl { + BeforeStep { _ => + invoke() + } + } - val hook = Befores.registry.beforeHooks.head - hook.execute(testCaseTest) + val glue = new Glue() - assertEquals(testCaseTest, actualScenario.delegate) - assertEquals(1000, hook.getOrder) - assertEquals("", hook.getTagExpression) + assertClassHook(glue.registry.beforeStepHooks.head, "", 1000) } @Test - def taggedBefore { - var actualScenario: Scenario = null + def testBeforeStepHookWithTag(): Unit = { - object Befores extends ScalaDsl with EN { - Before("(@foo or @bar) and @zap") { - actualScenario = _ + class Glue extends ScalaDsl { + BeforeStep("tagExpression") { _ => + invoke() } } - assertEquals(1, Befores.registry.beforeHooks.size) + val glue = new Glue() + + assertClassHook(glue.registry.beforeStepHooks.head, "tagExpression", 1000) + } + + @Test + def testBeforeStepHookWithOrder(): Unit = { + + class Glue extends ScalaDsl { + BeforeStep(42) { _ => + invoke() + } + } - val hook = Befores.registry.beforeHooks.head - hook.execute(testCaseTest) + val glue = new Glue() - assertEquals(testCaseTest, actualScenario.delegate) - assertEquals(1000, hook.getOrder) - assertEquals("(@foo or @bar) and @zap", hook.getTagExpression) + assertClassHook(glue.registry.beforeStepHooks.head, "", 42) } @Test - def orderedBefore { + def testBeforeStepHookWithTagAndOrder(): Unit = { - object Befores extends ScalaDsl with EN { - Before(10) { scenario: Scenario => } + class Glue extends ScalaDsl { + BeforeStep("tagExpression", 42) { _ => + invoke() + } } - val hook = Befores.registry.beforeHooks(0) - assertEquals(10, hook.getOrder) - assertEquals("", hook.getTagExpression) + val glue = new Glue() + + assertClassHook(glue.registry.beforeStepHooks.head, "tagExpression", 42) } + @Test - def taggedOrderedBefore { + def testAfterHook(): Unit = { - object Befores extends ScalaDsl with EN { - Before("(@foo or @bar) and @zap", 10) { scenario: Scenario => } + class Glue extends ScalaDsl { + After { _ => + invoke() + } } - val hook = Befores.registry.beforeHooks(0) - assertEquals(10, hook.getOrder) - assertEquals("(@foo or @bar) and @zap", hook.getTagExpression) + val glue = new Glue() + + assertClassHook(glue.registry.afterHooks.head, "", 1000) } @Test - def emptyAfter { + def testAfterHookWithTag(): Unit = { - var actualScenario: Scenario = null + class Glue extends ScalaDsl { + After("tagExpression") { _ => + invoke() + } + } - object Afters extends ScalaDsl with EN { - After { - actualScenario = _ + val glue = new Glue() + + assertClassHook(glue.registry.afterHooks.head, "tagExpression", 1000) + } + + @Test + def testAfterHookWithOrder(): Unit = { + + class Glue extends ScalaDsl { + After(42) { _ => + invoke() } } - assertEquals(1, Afters.registry.afterHooks.size) + val glue = new Glue() + + assertClassHook(glue.registry.afterHooks.head, "", 42) + } - val hook = Afters.registry.afterHooks.head - hook.execute(testCaseTest) + @Test + def testAfterHookWithTagAndOrder(): Unit = { - assertEquals(testCaseTest, actualScenario.delegate) - assertEquals(1000, hook.getOrder) - assertEquals("", hook.getTagExpression) + class Glue extends ScalaDsl { + After("tagExpression", 42) { _ => + invoke() + } + } + + val glue = new Glue() + + assertClassHook(glue.registry.afterHooks.head, "tagExpression", 42) } @Test - def taggedAfter { - var actualScenario: Scenario = null + def testAfterStepHook(): Unit = { - object Afters extends ScalaDsl with EN { - After("(@foo or @bar) and @zap") { - actualScenario = _ + class Glue extends ScalaDsl { + AfterStep { _ => + invoke() } } - assertEquals(1, Afters.registry.afterHooks.size) + val glue = new Glue() + + assertClassHook(glue.registry.afterStepHooks.head, "", 1000) + } + + @Test + def testAfterStepHookWithTag(): Unit = { + + class Glue extends ScalaDsl { + AfterStep("tagExpression") { _ => + invoke() + } + } - val hook = Afters.registry.afterHooks.head - hook.execute(testCaseTest) + val glue = new Glue() - assertEquals(testCaseTest, actualScenario.delegate) - assertEquals(1000, hook.getOrder) - assertEquals("(@foo or @bar) and @zap", hook.getTagExpression) + assertClassHook(glue.registry.afterStepHooks.head, "tagExpression", 1000) } @Test - def noArg { - var called = false + def testAfterStepHookWithOrder(): Unit = { - object Dummy extends ScalaDsl with EN { - // One line to avoid difference in lineNumber between Scala versions - Given("x") { called = true } + class Glue extends ScalaDsl { + AfterStep(42) { _ => + invoke() + } } - assertEquals(1, Dummy.registry.stepDefinitions.size) + val glue = new Glue() + + assertClassHook(glue.registry.afterStepHooks.head, "", 42) + } - val step = Dummy.registry.stepDefinitions.head + @Test + def testAfterStepHookWithTagAndOrder(): Unit = { - assertEquals("ScalaDslTest.scala:145", step.getLocation) // be careful with formatting or this test will break - assertEquals("x", step.getPattern) + class Glue extends ScalaDsl { + AfterStep("tagExpression", 42) { _ => + invoke() + } + } - step.execute(Array()) + val glue = new Glue() - assertTrue(called) + assertClassHook(glue.registry.afterStepHooks.head, "tagExpression", 42) } @Test - def args { + def testDefNoArg(): Unit = { + + var invoked = false + + class Glue extends ScalaDsl with EN { + // On a single line to avoid difference between Scala versions for the location + //@formatter:off + Given("Something") { invoked = true } + //@formatter:on + } + + val glue = new Glue() + + assertClassStepDefinition(glue.registry.stepDefinitions.head, "Something", "ScalaDslTest.scala:261", Array(), invoked) + } + + @Test + def testDefEmptyArg(): Unit = { + + var invoked = false + + class Glue extends ScalaDsl with EN { + Given("Something") { () => + invoked = true + } + } + + val glue = new Glue() + + assertClassStepDefinition(glue.registry.stepDefinitions.head, "Something", "ScalaDslTest.scala:276", Array(), invoked) + } + + @Test + def testDefWithArgs(): Unit = { + var thenumber = 0 var thecolour = "" - object Dummy extends ScalaDsl with EN { - Given("Oh boy, (\\d+) (\\s+) cukes") { (num: Int, colour: String) => + class Glue extends ScalaDsl with EN { + Given("""Oh boy, (\d+) (\s+) cukes""") { (num: Int, colour: String) => thenumber = num thecolour = colour } } - assertEquals(1, Dummy.registry.stepDefinitions.size) + val glue = new Glue() - val step = Dummy.registry.stepDefinitions.head - step.execute(Array(new java.lang.Integer(5), "green")) + assertClassStepDefinition(glue.registry.stepDefinitions.head, """Oh boy, (\d+) (\s+) cukes""", "ScalaDslTest.scala:293", Array(new java.lang.Integer(5), "green"), thenumber == 5 && thecolour == "green") + } + + @Test + def testDefThrowException(): Unit = { + + class GlueWithException extends ScalaDsl with EN { + Given("Something") { () => + val x = 1 + 2 // A not useful line + throw new PendingException() + } + } + + val glue = new GlueWithException() + + assertClassStepDefinitionThrow(glue.registry.stepDefinitions.head, "io.cucumber.scala.ScalaDslTest$GlueWithException", "ScalaDslTest.scala", 310, Array()) + } + + // -------------------- Test on object -------------------- + // Note: for now there is no difference between the two in ScalaDsl but better safe than sorry + + @Test + def testObjectBeforeHook(): Unit = { - assertEquals(5, thenumber) - assertEquals("green", thecolour) + object Glue extends ScalaDsl { + Before { _ => + invoke() + } + } + + assertObjectHook(Glue.registry.beforeHooks.head, "", 1000) + } + + @Test + def testObjectBeforeHookWithTag(): Unit = { + + object Glue extends ScalaDsl { + Before("tagExpression") { _ => + invoke() + } + } + + assertObjectHook(Glue.registry.beforeHooks.head, "tagExpression", 1000) + } + + @Test + def testObjectBeforeHookWithOrder(): Unit = { + + object Glue extends ScalaDsl { + Before(42) { _ => + invoke() + } + } + + assertObjectHook(Glue.registry.beforeHooks.head, "", 42) + } + + @Test + def testObjectBeforeHookWithTagAndOrder(): Unit = { + + object Glue extends ScalaDsl { + Before("tagExpression", 42) { _ => + invoke() + } + } + + assertObjectHook(Glue.registry.beforeHooks.head, "tagExpression", 42) + } + + @Test + def testObjectBeforeStepHook(): Unit = { + + object Glue extends ScalaDsl { + BeforeStep { _ => + invoke() + } + } + + assertObjectHook(Glue.registry.beforeStepHooks.head, "", 1000) + } + + @Test + def testObjectBeforeStepHookWithTag(): Unit = { + + object Glue extends ScalaDsl { + BeforeStep("tagExpression") { _ => + invoke() + } + } + + assertObjectHook(Glue.registry.beforeStepHooks.head, "tagExpression", 1000) + } + + @Test + def testObjectBeforeStepHookWithOrder(): Unit = { + + object Glue extends ScalaDsl { + BeforeStep(42) { _ => + invoke() + } + } + + assertObjectHook(Glue.registry.beforeStepHooks.head, "", 42) + } + + @Test + def testObjectBeforeStepHookWithTagAndOrder(): Unit = { + + object Glue extends ScalaDsl { + BeforeStep("tagExpression", 42) { _ => + invoke() + } + } + + assertObjectHook(Glue.registry.beforeStepHooks.head, "tagExpression", 42) + } + + + @Test + def testObjectAfterHook(): Unit = { + + object Glue extends ScalaDsl { + After { _ => + invoke() + } + } + + assertObjectHook(Glue.registry.afterHooks.head, "", 1000) + } + + @Test + def testObjectAfterHookWithTag(): Unit = { + + object Glue extends ScalaDsl { + After("tagExpression") { _ => + invoke() + } + } + + assertObjectHook(Glue.registry.afterHooks.head, "tagExpression", 1000) + } + + @Test + def testObjectAfterHookWithOrder(): Unit = { + + object Glue extends ScalaDsl { + After(42) { _ => + invoke() + } + } + + assertObjectHook(Glue.registry.afterHooks.head, "", 42) + } + + @Test + def testObjectAfterHookWithTagAndOrder(): Unit = { + + object Glue extends ScalaDsl { + After("tagExpression", 42) { _ => + invoke() + } + } + + assertObjectHook(Glue.registry.afterHooks.head, "tagExpression", 42) + } + + @Test + def testObjectAfterStepHook(): Unit = { + + object Glue extends ScalaDsl { + AfterStep { _ => + invoke() + } + } + + assertObjectHook(Glue.registry.afterStepHooks.head, "", 1000) + } + + @Test + def testObjectAfterStepHookWithTag(): Unit = { + + object Glue extends ScalaDsl { + AfterStep("tagExpression") { _ => + invoke() + } + } + + assertObjectHook(Glue.registry.afterStepHooks.head, "tagExpression", 1000) + } + + @Test + def testObjectAfterStepHookWithOrder(): Unit = { + + object Glue extends ScalaDsl { + AfterStep(42) { _ => + invoke() + } + } + + assertObjectHook(Glue.registry.afterStepHooks.head, "", 42) + } + + @Test + def testObjectAfterStepHookWithTagAndOrder(): Unit = { + + object Glue extends ScalaDsl { + AfterStep("tagExpression", 42) { _ => + invoke() + } + } + + assertObjectHook(Glue.registry.afterStepHooks.head, "tagExpression", 42) + } + + @Test + def testObjectDefNoArg(): Unit = { + + var invoked = false + + object Glue extends ScalaDsl with EN { + // On a single line to avoid difference between Scala versions for the location + //@formatter:off + Given("Something") { invoked = true } + //@formatter:on + } + + assertObjectStepDefinition(Glue.registry.stepDefinitions.head, "Something", "ScalaDslTest.scala:523", Array(), invoked) + } + + @Test + def testObjectDefEmptyArg(): Unit = { + + var invoked = false + + object Glue extends ScalaDsl with EN { + Given("Something") { () => + invoked = true + } + } + + assertObjectStepDefinition(Glue.registry.stepDefinitions.head, "Something", "ScalaDslTest.scala:536", Array(), invoked) + } + + @Test + def testObjectDefWithArgs(): Unit = { + + var thenumber = 0 + var thecolour = "" + + object Glue extends ScalaDsl with EN { + Given("""Oh boy, (\d+) (\s+) cukes""") { (num: Int, colour: String) => + thenumber = num + thecolour = colour + } + } + + assertObjectStepDefinition(Glue.registry.stepDefinitions.head, """Oh boy, (\d+) (\s+) cukes""", "ScalaDslTest.scala:551", Array(new java.lang.Integer(5), "green"), thenumber == 5 && thecolour == "green") + } + + @Test + def testObjectDefThrowException(): Unit = { + + object GlueWithException extends ScalaDsl with EN { + Given("Something") { () => + val x = 1 + 2 // A not useful line + throw new PendingException() + } + } + + assertObjectStepDefinitionThrow(GlueWithException.registry.stepDefinitions.head, "io.cucumber.scala.ScalaDslTest$GlueWithException", "ScalaDslTest.scala", 566, Array()) + } + + private def assertClassHook(hookDetails: ScalaHookDetails, tagExpression: String, order: Int): Unit = { + assertHook(ScalaHookDefinition(hookDetails, true), tagExpression, order) + } + + private def assertObjectHook(hookDetails: ScalaHookDetails, tagExpression: String, order: Int): Unit = { + assertHook(ScalaHookDefinition(hookDetails, false), tagExpression, order) + } + + private def assertHook(hook: HookDefinition, tagExpression: String, order: Int): Unit = { + assertEquals(tagExpression, hook.getTagExpression) + assertEquals(order, hook.getOrder) + hook.execute(fakeState) + assertTrue(invoked.get()) + } + + private def assertClassStepDefinition(stepDetails: ScalaStepDetails, pattern: String, location: String, args: Array[AnyRef], check: => Boolean): Unit = { + assertStepDefinition(ScalaStepDefinition(stepDetails, true), pattern, location, args, check) + } + + private def assertObjectStepDefinition(stepDetails: ScalaStepDetails, pattern: String, location: String, args: Array[AnyRef], check: => Boolean): Unit = { + assertStepDefinition(ScalaStepDefinition(stepDetails, false), pattern, location, args, check) + } + + private def assertStepDefinition(stepDefinition: StepDefinition, pattern: String, location: String, args: Array[AnyRef], check: => Boolean): Unit = { + assertEquals(pattern, stepDefinition.getPattern) + assertEquals(location, stepDefinition.getLocation) + stepDefinition.execute(args) + assertTrue(check) + } + + private def assertClassStepDefinitionThrow(stepDetails: ScalaStepDetails, exceptionClassName: String, exceptionFile: String, exceptionLine: Int, args: Array[AnyRef]): Unit = { + assertStepDefinitionThrow(ScalaStepDefinition(stepDetails, true), exceptionClassName, exceptionFile, exceptionLine, args) + } + + private def assertObjectStepDefinitionThrow(stepDetails: ScalaStepDetails, exceptionClassName: String, exceptionFile: String, exceptionLine: Int, args: Array[AnyRef]): Unit = { + assertStepDefinitionThrow(ScalaStepDefinition(stepDetails, false), exceptionClassName, exceptionFile, exceptionLine, args) + } + + private def assertStepDefinitionThrow(stepDefinition: StepDefinition, exceptionClassName: String, exceptionFile: String, exceptionLine: Int, args: Array[AnyRef]): Unit = { + val tried = Try(stepDefinition.execute(args)) + + assertTrue(tried.isFailure) + + val ex = tried.failed.get + assertTrue(ex.isInstanceOf[CucumberInvocationTargetException]) + + val matched = ex.asInstanceOf[CucumberInvocationTargetException] + .getInvocationTargetExceptionCause + .getStackTrace + .filter(stepDefinition.isDefinedAt) + .head + + // The result is different between Scala versions, that's why we don't check it precisely + //assertEquals("$anonfun$can_provide_location_of_step$1", matched.getMethodName) + assertTrue(matched.getClassName.contains(exceptionClassName)) + assertEquals(exceptionFile, matched.getFileName) + assertEquals(exceptionLine, matched.getLineNumber) } } + diff --git a/scala/sources/src/test/scala/io/cucumber/scala/ScalaHookDefinitionTest.scala b/scala/sources/src/test/scala/io/cucumber/scala/ScalaHookDefinitionTest.scala deleted file mode 100644 index b615ac03..00000000 --- a/scala/sources/src/test/scala/io/cucumber/scala/ScalaHookDefinitionTest.scala +++ /dev/null @@ -1,19 +0,0 @@ -package io.cucumber.scala - -import org.junit.Assert.assertTrue -import org.junit.Test - -class ScalaHookDefinitionTest { - - @Test - def can_create_with_single_scenario_argument() = { - var invoked = false - val body = (scenario: Scenario) => { - invoked = true - } - val definition = new ScalaHookDefinition("", 0, body) - definition.execute(null) - assertTrue(invoked) - } - -} diff --git a/scala/sources/src/test/scala/io/cucumber/scala/ScalaStepDefinitionTest.scala b/scala/sources/src/test/scala/io/cucumber/scala/ScalaStepDefinitionTest.scala deleted file mode 100644 index 31ce2767..00000000 --- a/scala/sources/src/test/scala/io/cucumber/scala/ScalaStepDefinitionTest.scala +++ /dev/null @@ -1,46 +0,0 @@ -package io.cucumber.scala - -import io.cucumber.core.backend.CucumberInvocationTargetException -import org.junit.Test -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue - -import scala.util.Try - -class ScalaStepDefinitionTest { - - @Test - def can_define_step() = { - var args: List[Any] = List() - val body = (params: List[Any]) => { - args = params - } - val definition = new ScalaStepDefinition(null, "whatever", "three (.*) mice", Array(classOf[String]), body) - definition.execute(Array("one_string_argument")) - - assertEquals("one_string_argument", args.head) - } - - @Test - def can_provide_location_of_step() = { - val body = (_: List[Any]) => { - throw new PendingException() - } - val frame = Utils.frame(this) - val definition = new ScalaStepDefinition(frame, "whatever", "three (.*) mice", Array(), body) - - val tried = Try(definition.execute(Array())) - - assertTrue(tried.isFailure) - val ex = tried.failed.get - assertTrue(ex.isInstanceOf[CucumberInvocationTargetException]) - val matched = ex.asInstanceOf[CucumberInvocationTargetException].getInvocationTargetExceptionCause.getStackTrace.filter(definition.isDefinedAt).head - - // Method name in Scala does not make much sense - //assertEquals("$anonfun$can_provide_location_of_step$1", matched.getMethodName) - //assertEquals(classOf[ScalaStepDefinitionTest].getName, matched.getClassName) - assertEquals("ScalaStepDefinitionTest.scala", matched.getFileName) - assertEquals(27, matched.getLineNumber) - } - -} diff --git a/scala/sources/src/test/scala/io/cucumber/scala/steps/Steps.scala b/scala/sources/src/test/scala/io/cucumber/scala/steps/Steps.scala deleted file mode 100644 index 1ad34921..00000000 --- a/scala/sources/src/test/scala/io/cucumber/scala/steps/Steps.scala +++ /dev/null @@ -1,11 +0,0 @@ -package io.cucumber.scala.steps - -import io.cucumber.scala.{EN, ScalaDsl} - -class Steps extends ScalaDsl with EN { - - Given("""test""") { () => - // Nothing - } - -} diff --git a/scala/sources/src/test/scala/io/cucumber/scala/steps/classes/MultipleInSameFile.scala b/scala/sources/src/test/scala/io/cucumber/scala/steps/classes/MultipleInSameFile.scala new file mode 100644 index 00000000..5ab0f251 --- /dev/null +++ b/scala/sources/src/test/scala/io/cucumber/scala/steps/classes/MultipleInSameFile.scala @@ -0,0 +1,35 @@ +package io.cucumber.scala.steps.classes + +import io.cucumber.scala.{EN, ScalaDsl} + +class StepsA extends ScalaDsl with EN { + + Before { _ => + // Nothing + } + + BeforeStep { _ => + // Nothing + } + + After { _ => + // Nothing + } + + AfterStep { _ => + // Nothing + } + + Given("""stepA""") { () => + // Nothing + } + +} + +class StepsB extends ScalaDsl with EN { + + When("""stepsB""") { () => + // Nothing + } + +} diff --git a/scala/sources/src/test/scala/io/cucumber/scala/steps/classes/SingleFile.scala b/scala/sources/src/test/scala/io/cucumber/scala/steps/classes/SingleFile.scala new file mode 100644 index 00000000..f7519b78 --- /dev/null +++ b/scala/sources/src/test/scala/io/cucumber/scala/steps/classes/SingleFile.scala @@ -0,0 +1,11 @@ +package io.cucumber.scala.steps.classes + +import io.cucumber.scala.{EN, ScalaDsl} + +class StepsC extends ScalaDsl with EN { + + Then("""stepsC""") { () => + // Nothing + } + +} diff --git a/scala/sources/src/test/scala/io/cucumber/scala/steps/objects/StepsInObject.scala b/scala/sources/src/test/scala/io/cucumber/scala/steps/objects/StepsInObject.scala new file mode 100644 index 00000000..035a4443 --- /dev/null +++ b/scala/sources/src/test/scala/io/cucumber/scala/steps/objects/StepsInObject.scala @@ -0,0 +1,27 @@ +package io.cucumber.scala.steps.objects + +import io.cucumber.scala.{EN, ScalaDsl} + +object StepsInObject extends ScalaDsl with EN { + + Before { _ => + // Nothing + } + + BeforeStep { _ => + // Nothing + } + + After { _ => + // Nothing + } + + AfterStep { _ => + // Nothing + } + + Given("""Given step""") { () => + // Nothing + } + +} diff --git a/scala/sources/src/test/scala/io/cucumber/scala/steps/traits/StepsInTrait.scala b/scala/sources/src/test/scala/io/cucumber/scala/steps/traits/StepsInTrait.scala new file mode 100644 index 00000000..bbf8a3f1 --- /dev/null +++ b/scala/sources/src/test/scala/io/cucumber/scala/steps/traits/StepsInTrait.scala @@ -0,0 +1,31 @@ +package io.cucumber.scala.steps.traits + +import io.cucumber.scala.{EN, ScalaDsl} + +trait TraitWithSteps extends ScalaDsl with EN { + + Before { _ => + // Nothing + } + + BeforeStep { _ => + // Nothing + } + + After { _ => + // Nothing + } + + AfterStep { _ => + // Nothing + } + + Given("""Given step""") { () => + // Nothing + } + +} + +class StepsInTrait extends TraitWithSteps { + +} From 27449e2a7d8c82da37459df31613da5b00b61fd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Jourdan-Weil?= Date: Sun, 26 Apr 2020 16:04:47 +0200 Subject: [PATCH 4/6] Add documentation --- README.md | 7 +++++ docs/project_structure.md | 50 ++++++++++++++++++++++++++++++ docs/scala_implementation.md | 59 ++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 docs/project_structure.md create mode 100644 docs/scala_implementation.md diff --git a/README.md b/README.md index 1b507687..11fe9c73 100644 --- a/README.md +++ b/README.md @@ -42,3 +42,10 @@ Any contribution is welcome: - developing a new feature Please use this Github project for contributing, either through an issue or a Pull Request. + +### Documentation + +These pages aim to help Cucumber Scala developers understand the codebase. + +- [Scala implementation details](docs/scala_implementation.md) +- [Project structure](docs/project_structure.md) diff --git a/docs/project_structure.md b/docs/project_structure.md new file mode 100644 index 00000000..43f29950 --- /dev/null +++ b/docs/project_structure.md @@ -0,0 +1,50 @@ +# Project structure + +The Cucumber Scala project is a Maven multimodule project: +- `scala` module: contains the codebase of the Cucumber Scala implementation + - `scala_2.11` submodule: build for Scala 2.11.x + - `scala_2.12` submodule: build for Scala 2.12.x + - `scala_2.13` submodule: build for Scala 2.13.x +- `examples` module: contains a sample project + +## Cross compilation + +The cross compilation for the different Scala versions is handled with 3 different Maven projects: the submodules of the `scala` module. + +Each project has a different Scala version as dependency: +```xml + + org.scala-lang + scala-compiler + ${scala.2.13.version} + provided + +``` + +To not copy/paste the sources across the 3 projects, the sources are put in a separated folder called `sources` in the `scala` module. +Each project uses it by defining the following properties: +```xml +../sources/src/main/scala + + + ../sources/src/main/resources + + +../sources/src/test/scala + + + ../sources/src/test/resources + + +``` + +**Note:** when using your favorite IDE, you might have to "close" or "unload" 2 of the 3 projects. +Some IDE are not able to handle shared sources because a source path can be attached to a single IDE project. +If so, only loading the latest (`scala_2.13` project) is recommended. + +## Language traits generation + +The language traits (`io.cucumber.scala.EN` for instance) are generated automatically using +a Groovy script at compile time. + +See in `sources/src/main/groovy/` folder. diff --git a/docs/scala_implementation.md b/docs/scala_implementation.md new file mode 100644 index 00000000..854c8d0d --- /dev/null +++ b/docs/scala_implementation.md @@ -0,0 +1,59 @@ +# Scala implementation details + +This page covers some details about the Cucumber Scala implementation. + +## Running a Cucumber test + +### Backend + +From Cucumber core perspective, the entrypoint of a Cucumber implementation is what is called "backend". + +The `BackendServiceLoader` core service looks for a `BackendProviderService` implementation. +Ours is defined in the class `ScalaBackendProviderService`. + +The implementing class also has to be registered as a "Java Service" in the `META-INF/services/io.cucumber.core.backend.BackendProviderService` file (in the `resources` folder). + +### Loading the glue + +When a Cucumber test starts, a Cucumber Runner starts and a `ScalaBackend` instance is created. +The `ScalaBackend` instance will be used for running all the scenarios which are part of the test (defined by the _features path_ and the _glue path_). + +The first thing the Runner does is to "load the glue", that is find all the hooks and step definitions and register them. +This is handled by the `ScalaBackend#loadGlue()` method. + +#### Scala implementation + +In the Cucumber Scala implementation, loading the glue code means: +- finding all the **classes** inheriting `io.cucumber.scala.ScalaDsl` in the _glue path_, and for each: + - add it to the `Container` instance provided by Cucumber Core +- finding all the **objects** singletons instances inheriting `io.cucumber.scala.ScalaDsl` in the _glue path_ and for each: + - extract the hooks and step definitions from it + - add the definitions to the `Glue` instance provided by Cucumber Core, as NOT `ScenarioScoped` + +Ideally all the glue code should be instantiated further (see next section), this is why we register classes (actually a list of `Class`) to the Container. +But this cannot work for objects because they are by definitions singletons and already instantiated way before Cucumber. +Thus, objects are not registered in the Container and their lifecycle is out of Cucumber scope. + +### Running a scenario + +For each scenario, the `buildWorld()` method of the backend is called. +This is where the glue code should be initialized. + +#### Scala implementation + +For each **class** identified when loading the glue: +- an instance is created by the `Lookup` provided by Cucumber Core +- hooks and steps definitions are extracted from it +- definitions are added to the `Glue` instance provided by Cucumber Core, as `ScenarioScoped` + +Being `ScenarioScoped` ensure instances are flushed at the end of the scenario and recreated for the next one. + +## Scala DSL + +The Scala DSL is made in a way that any class instance or object extending it contains what we call a **registry**: +a list of the hooks and step definitions it contains. +This is the purpose of `ScalaDslRegistry`. + +The registry is populated when the class instance or the object is created. +Unlike other implementations there is no need to use annotations or reflection here. +This is actually **similar to the Java8/Lambda implementation**. From 319b1090a690f61923de2add475fe3d3b95f6b3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Jourdan-Weil?= Date: Fri, 1 May 2020 11:35:37 +0200 Subject: [PATCH 5/6] Add upgrade documentation --- docs/upgrade_v5.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/upgrade_v5.md b/docs/upgrade_v5.md index 73be200e..0fba4499 100644 --- a/docs/upgrade_v5.md +++ b/docs/upgrade_v5.md @@ -51,3 +51,17 @@ Before { _ => ``` See also the [Hooks documentation](hooks.md). + +## Under the hood + +### Instantiate glue classes per scenario + +Before Cucumber Scala 5.x, glue classes (classes extending `ScalaDsl`) were instantiated only once for a test suite. + +This means that if you wanted to keep state between steps of your scenarios, you had to make sure the state was not shared to other scenarios by using hooks or manual checks. + +Starting from Cucumber Scala 5.x, **each scenario creates new glue class instances**. + +You should not notice any change unless you rely on state kept between scenarios in your glue classes. +Please note that this is not the proper way to keep a state. +You might want to use an `object` for this purpose. From c0a78e930e7f5208d1e6509ed9705a247450ce5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Jourdan-Weil?= Date: Fri, 1 May 2020 11:43:16 +0200 Subject: [PATCH 6/6] Complete CHANGELOG (and add references) --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a02a697a..b5b27baa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,8 @@ See also the [CHANGELOG](https://github.com/cucumber/cucumber-jvm/blob/master/CH ### Fixed +- [Core] Instantiate glue classes per scenario ([#1](https://github.com/cucumber/cucumber-jvm-scala/issues/1) Gaƫl Jourdan-Weil) + ### Security ## [4.7.1] (2019-08-01) @@ -96,3 +98,16 @@ See also the [CHANGELOG](https://github.com/cucumber/cucumber-jvm/blob/master/CH - [Core] Update `cucumber-core` dependency to 4.1.0 (Glib Briia) - [Build] Update Scala versions to 2.11.12 and 2.12.7 ([#11](https://github.com/cucumber/cucumber-jvm-scala/issues/11) Arturas Smorgun) + + +[Unreleased]: https://github.com/cucumber/cucumber-jvm-scala/compare/v4.7.1...master +[4.7.1]: https://github.com/cucumber/cucumber-jvm-scala/compare/v4.7.0...v4.7.1 +[4.7.0]: https://github.com/cucumber/cucumber-jvm-scala/compare/v4.6.0...v4.7.0 +[4.6.0]: https://github.com/cucumber/cucumber-jvm-scala/compare/v4.5.4...v4.6.0 +[4.5.4]: https://github.com/cucumber/cucumber-jvm-scala/compare/v4.5.3...v4.5.4 +[4.5.3]: https://github.com/cucumber/cucumber-jvm-scala/compare/v4.4.0...v4.5.3 +[4.4.0]: https://github.com/cucumber/cucumber-jvm-scala/compare/v4.3.1...v4.4.0 +[4.3.1]: https://github.com/cucumber/cucumber-jvm-scala/compare/v4.3.0...v4.3.1 +[4.3.0]: https://github.com/cucumber/cucumber-jvm-scala/compare/v4.2.6...v4.3.0 +[4.2.6]: https://github.com/cucumber/cucumber-jvm-scala/compare/v4.2.0...v4.2.6 +[4.2.0]: https://github.com/cucumber/cucumber-jvm-scala/compare/v4.1.0...v4.2.0 \ No newline at end of file