Skip to content

Commit 42d0f01

Browse files
committed
Improved the integration of cucumber into the test task.
- The integration project now depends on the correct cucumber version - The integration project is added as a dependency jar to a project by the plugin (thus pulling in the correct cucumber version) - Cucumber is now launched with less reflection - System.out is now no longer closed when the cucumber tests finish running - README updated with the changes
1 parent 6fa47a9 commit 42d0f01

File tree

8 files changed

+124
-152
lines changed

8 files changed

+124
-152
lines changed

README.markdown

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ It adds the ability to run cucumber as a standalone SBT task but also as a test
99
### EXPERIMENTAL RELEASE ###
1010
This is currently an experimental release of the ability to run cucumber as a standard SBT test framework. The basic mechanisms are in place, but currently the following are broken / do not work:
1111

12-
* When running the 'test' target, cucumber runs but then seems to lock up rather than return control back to SBT [Major]
13-
* Currently does not work with Scala 2.10.0-RC1 (I've only been testing against 2.9.2 projects and need to cross-build the integration project to allow it to work)
12+
* Currently MAY not work with Scala 2.10.0-RC1 (I've only been testing against 2.9.2 projects and need to cross-build the integration project to allow it to work)
1413
* There is currently no support for 'test-only' and no way to just run features with specific names or tags
1514
* When running in a multi-project setup, the cucumbers for each project are run in parallel and the output is interleaved and thus unreadable.
1615

@@ -119,16 +118,10 @@ To add the cucumber plugin settings to a basic project, just add the following t
119118
The testProjects/testProject in the plugin source repository shows this configuration.
120119

121120
#### Running as a test framework ####
122-
If you wish to support cucumber running as a test framework (via the test task) then the following settings should be placed in the build.sbt file instead:
123-
124-
libraryDependencies ++= Seq(
125-
"templemore" %% "sbt-cucumber-integration" % "0.7.0" % "test"
126-
)
121+
If you wish to support cucumber running as a test framework (via the test task) then use this alternative settings group instead:
127122

128123
seq(cucumberSettingsWithTestPhaseIntegration : _*)
129124

130-
Note that cucumberSettingsWithTestPhaseIntegration replaces cucumberSettings in this case. The testProjects/testIntegrationProject in the plugin source repository showns this configuration.
131-
132125
### Full Configuration ###
133126
To add the cucumber plugin settings to a full configuration (often a multi-module) project, the best way is to implement a project/Build.scala file:
134127

@@ -149,12 +142,9 @@ The testProjects/multiModuleTestProject in the plugin source repository shows th
149142
#### Running as a test framework ####
150143
If you wish to support cucumber running as a test framework (via the test task) then the following settings should be placed in the build file instead:
151144

152-
val testIntegration = "templemore" %% "sbt-cucumber-integration" % "0.7.0" % "test"
153-
154145
lazy val myProject = Project ("my-project", file ("."),
155146
settings = Defaults.defaultSettings ++
156-
CucumberPlugin.cucumberSettingsWithTestPhaseIntegration ++
157-
Seq(libraryDependencies ++= Seq(testIntegration)))
147+
CucumberPlugin.cucumberSettingsWithTestPhaseIntegration)
158148

159149
## Customisation ##
160150
The plugin supports a number of customisations and settings. Note that these setting customisations only apply to running using the standalone 'cucumber' task. Running cucumber as a test framework does not support any customisation options.

integration/src/main/scala/templemore/sbt/cucumber/CucumberFramework.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class CucumberFramework extends Framework {
2121
}
2222

2323
class CucumberRunner(testClassLoader: ClassLoader, loggers: Array[Logger]) extends Runner2 {
24-
private val cucumber = new ReflectingCucumberLauncher(debug = logDebug, error = logError)
24+
private val cucumber = new CucumberLauncher(debug = logDebug, error = logError)
2525

2626
def run(testClassName: String, fingerprint: Fingerprint, eventHandler: EventHandler, args: Array[String]) = try {
2727
val arguments = Array("--glue", "", "--format", "pretty", "classpath:")
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package templemore.sbt.cucumber
2+
3+
import scala.collection.JavaConverters._
4+
import java.lang.reflect.InvocationTargetException
5+
import java.util.Properties
6+
7+
import cucumber.runtime.Runtime
8+
import cucumber.runtime.RuntimeOptions
9+
import cucumber.runtime.snippets.SummaryPrinter
10+
import cucumber.runtime.model.CucumberFeature
11+
import gherkin.formatter.Formatter
12+
import gherkin.formatter.Reporter
13+
14+
class CucumberLauncher(debug: (String) => Unit, error: (String) => Unit) {
15+
16+
private val MultiLoaderClassName = "cucumber.runtime.io.MultiLoader"
17+
private val MultiLoaderClassName_1_0_9 = "cucumber.io.MultiLoader"
18+
19+
def apply(cucumberArguments: Array[String],
20+
testClassLoader: ClassLoader): Int = {
21+
debug("Cucumber arguments: " + cucumberArguments.mkString(" "))
22+
val runtime = buildRuntime(System.getProperties, cucumberArguments, testClassLoader)
23+
runCucumber(runtime).asInstanceOf[Byte].intValue
24+
}
25+
26+
private def runCucumber(runtime: CucumberRuntime) = try {
27+
runtime.initialise
28+
runtime.run
29+
runtime.printSummary
30+
runtime.exitStatus
31+
} catch {
32+
case e: InvocationTargetException => {
33+
val cause = if ( e.getCause == null ) e else e.getCause
34+
error("Error running cucumber. Cause: " + cause.getMessage)
35+
throw cause
36+
}
37+
}
38+
39+
case class CucumberRuntime(runtime: Runtime, options: RuntimeOptions, loader: AnyRef,
40+
formatter: Formatter, reporter: Reporter, summaryPrinter: SummaryPrinter) {
41+
private val loaderClass = loader.getClass.getInterfaces()(0)
42+
43+
def initialise = runtime.writeStepdefsJson()
44+
def printSummary = summaryPrinter.print(runtime)
45+
def exitStatus = runtime.exitStatus
46+
47+
def run = {
48+
val featureList = (classOf[RuntimeOptions].getMethod("cucumberFeatures", loaderClass)
49+
.invoke(options, loader)
50+
.asInstanceOf[java.util.List[CucumberFeature]]
51+
.asScala)
52+
featureList foreach { feature => feature.run(formatter, reporter, runtime) }
53+
}
54+
}
55+
56+
private def buildRuntime(properties: Properties,
57+
arguments: Array[String],
58+
classLoader: ClassLoader): CucumberRuntime = try {
59+
def buildLoader(clazz: Class[_]) =
60+
clazz.getConstructor(classOf[ClassLoader]).newInstance(classLoader).asInstanceOf[AnyRef]
61+
62+
val loaderClass = loadCucumberClasses(classLoader)
63+
64+
val options = new RuntimeOptions(properties, arguments :_*)
65+
val loader = buildLoader(loaderClass)
66+
67+
val runtimeConstructor = classOf[Runtime].getConstructor(loaderClass.getInterfaces()(0), classOf[ClassLoader], classOf[RuntimeOptions])
68+
val runtime = runtimeConstructor.newInstance(loader, classLoader, options).asInstanceOf[Runtime]
69+
70+
CucumberRuntime(runtime, options, loader,
71+
options.formatter(classLoader), options.reporter(classLoader), new SummaryPrinter(System.out))
72+
} catch {
73+
case e =>
74+
error("Unable to construct cucumber runtime. Please report this as an error. (Details: " + e.getMessage + ")")
75+
throw e
76+
}
77+
78+
private def loadCucumberClasses(classLoader: ClassLoader) = try {
79+
val multiLoaderClassName = cucumberVersion(classLoader) match {
80+
case "1.0.9" => MultiLoaderClassName_1_0_9
81+
case _ => MultiLoaderClassName
82+
}
83+
84+
classLoader.loadClass(multiLoaderClassName)
85+
} catch {
86+
case e: ClassNotFoundException =>
87+
error("Unable to load Cucumber classes. Please check your project dependencies. (Details: " + e.getMessage + ")")
88+
throw e
89+
}
90+
91+
private def cucumberVersion(classLoader: ClassLoader) = {
92+
val stream = classLoader.getResourceAsStream("cucumber/version.properties")
93+
try {
94+
val props = new Properties()
95+
props.load(stream)
96+
val version = props.getProperty("cucumber-jvm.version")
97+
debug("Determined cucumber-jvm version to be: " + version)
98+
version
99+
} finally {
100+
stream.close()
101+
}
102+
}
103+
}

integration/src/main/scala/templemore/sbt/cucumber/ReflectingCucumberLauncher.scala

Lines changed: 0 additions & 120 deletions
This file was deleted.

plugin/src/main/scala/templemore/sbt/cucumber/CucumberPlugin.scala

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ import templemore.sbt.util._
1111
object CucumberPlugin extends Plugin with Integration {
1212

1313
private val projectVersion = "0.7.0"
14-
private val CucumberVersionForScala2_9 = "1.0.9"
15-
private val CucumberVersionForScala2_10 = "1.1.1"
1614

1715
type LifecycleCallback = () => Unit
1816

@@ -67,16 +65,11 @@ object CucumberPlugin extends Plugin with Integration {
6765
private def defaultBefore() = {}
6866
private def defaultAfter() = {}
6967

70-
private def cucumberVersion(scalaVersion: String) =
71-
if ( scalaVersion.startsWith("2.10") ) CucumberVersionForScala2_10 else CucumberVersionForScala2_9
72-
7368
private def cucumberMain(scalaVersion: String) =
7469
if ( scalaVersion.startsWith("2.10") ) "cucumber.api.cli.Main" else "cucumber.cli.Main"
7570

7671
val cucumberSettings: Seq[Setting[_]] = Seq(
77-
libraryDependencies <+= scalaVersion { sv =>
78-
"info.cukes" % "cucumber-scala" % cucumberVersion(sv) % "test"
79-
},
72+
libraryDependencies += "templemore" %% "sbt-cucumber-integration" % projectVersion % "test",
8073

8174
cucumber <<= inputTask(cucumberTask),
8275
cucumberTestSettings <<= cucumberSettingsTask,
@@ -90,7 +83,6 @@ object CucumberPlugin extends Plugin with Integration {
9083

9184
cucumberMainClass <<= (scalaVersion) { sv => cucumberMain(sv) },
9285
cucumberFeaturesLocation := "classpath:",
93-
// Replaced with cucumber on the classpath: cucumberFeaturesLocation <<= (baseDirectory) { (_ / "src" / "test" / "features").getPath },
9486
cucumberStepsBasePackage := "",
9587
cucumberExtraOptions := List.empty[String],
9688

@@ -109,9 +101,6 @@ object CucumberPlugin extends Plugin with Integration {
109101
)
110102

111103
val cucumberSettingsWithTestPhaseIntegration = cucumberSettings ++ Seq(
112-
libraryDependencies ++= Seq(
113-
"templemore" %% "sbt-cucumber-integration" % projectVersion % "test"
114-
),
115104
testFrameworks += new TestFramework("templemore.sbt.cucumber.CucumberFramework")
116105
)
117106
}

project/Build.scala

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,17 @@ object Settings {
1414
}
1515

1616
object Dependencies {
17-
val testInterface = "org.scala-tools.testing" % "test-interface" % "0.5"
17+
18+
private val CucumberVersionForScala2_9 = "1.0.9"
19+
private val CucumberVersionForScala2_10 = "1.1.1"
20+
21+
def cucumberScala(scalaVersion: String) = {
22+
def cucumberVersion = if ( scalaVersion.startsWith("2.10") ) CucumberVersionForScala2_10 else CucumberVersionForScala2_9
23+
"info.cukes" % "cucumber-scala" % cucumberVersion % "compile"
24+
}
25+
val cucumber = "info.cukes" % "cucumber-scala" % "1.0.9" % "compile"
26+
27+
val testInterface = "org.scala-tools.testing" % "test-interface" % "0.5" % "compile"
1828
}
1929

2030
object Build extends Build {
@@ -30,6 +40,8 @@ object Build extends Build {
3040

3141
lazy val integrationProject = Project ("sbt-cucumber-integration", file ("integration"),
3242
settings = buildSettings ++
33-
Seq(crossScalaVersions := Seq("2.9.2", "2.10.0-RC1"),
34-
libraryDependencies ++= Seq(testInterface)))
43+
Seq(crossScalaVersions := Seq("2.9.2", "2.10.0-RC1", "2.10.0-RC2"),
44+
libraryDependencies <+= scalaVersion { sv => cucumberScala(sv) },
45+
libraryDependencies += testInterface))
3546
}
47+

testProjects/testIntegrationProject/build.sbt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ organization := "templemore"
77
scalaVersion := "2.9.2"
88

99
libraryDependencies ++= Seq(
10-
"org.scalatest" %% "scalatest" % "1.7.2" % "test",
11-
"templemore" %% "sbt-cucumber-integration" % "0.7.0" % "test"
10+
"org.scalatest" %% "scalatest" % "1.7.2" % "test"
1211
)
1312

1413
seq(cucumberSettingsWithTestPhaseIntegration : _*)

testProjects/testProject/build.sbt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ libraryDependencies ++= Seq(
1010
"org.scalatest" %% "scalatest" % "1.7.2" % "test"
1111
)
1212

13-
1413
seq(cucumberSettings : _*)
1514

1615
cucumberStepsBasePackage := "test"

0 commit comments

Comments
 (0)