forked from corda/corda
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request corda#2500 from corda/tlil/bdd-test-framework
Experimental - BDD Test Framework
- Loading branch information
Showing
73 changed files
with
3,141 additions
and
1 deletion.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
# Introduction | ||
|
||
This project illustrates how one can use Cucumber / BDD to drive | ||
and test homogeneous and heterogeneous Corda networks on a local | ||
machine. The framework has built-in support for Dockerised node | ||
dependencies so that you easily can spin up a Corda node locally | ||
that, for instance, uses a 3rd party database provider such as | ||
MS SQL Server or Postgres. | ||
|
||
# Structure | ||
|
||
The project is split into three pieces: | ||
|
||
* **Testing Library** (main) - This library contains auxiliary | ||
functions that help in configuring and bootstrapping Corda | ||
networks on a local machine. The purpose of the library is to | ||
aid in black-box testing and automation. | ||
|
||
* **Unit Tests** (test) - These are various tests for the | ||
library described above. Note that there's only limited | ||
coverage for now. | ||
|
||
* **BDD Framework** (scenario) - This module shows how to use | ||
BDD-style frameworks to control the testing of Corda networks; | ||
more specifically, using [Cucumber](cucumber.io). | ||
|
||
# Setup | ||
|
||
To get started, please follow the instructions below: | ||
|
||
* Go up to the root directory and build the capsule JAR. | ||
|
||
```bash | ||
$ cd ../../ | ||
$ ./gradlew install | ||
``` | ||
|
||
* Come back to this folder and run: | ||
|
||
```bash | ||
$ cd experimental/behave | ||
$ ./prepare.sh | ||
``` | ||
|
||
This script will download necessary database drivers and set up | ||
the dependencies directory with copies of the Corda fat-JAR and | ||
the network bootstrapping tool. | ||
|
||
# Selective Runs | ||
|
||
If you only want to run tests of a specific tag, you can append | ||
the following parameter to the Gradle command: | ||
|
||
```bash | ||
$ ../../gradlew scenario -Ptags="@cash" | ||
# or | ||
$ ../../gradlew scenario -Ptags="@cash,@logging" | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
buildscript { | ||
ext.commonsio_version = '2.6' | ||
ext.commonslogging_version = '1.2' | ||
ext.cucumber_version = '1.2.5' | ||
ext.crash_version = 'cce5a00f114343c1145c1d7756e1dd6df3ea984e' | ||
ext.docker_client_version = '8.11.0' | ||
|
||
repositories { | ||
maven { | ||
jcenter() | ||
url 'https://jitpack.io' | ||
} | ||
} | ||
|
||
dependencies { | ||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" | ||
} | ||
} | ||
|
||
group 'net.corda.behave' | ||
|
||
apply plugin: 'java' | ||
apply plugin: 'kotlin' | ||
|
||
sourceCompatibility = 1.8 | ||
|
||
repositories { | ||
mavenCentral() | ||
} | ||
|
||
sourceSets { | ||
scenario { | ||
java { | ||
compileClasspath += main.output | ||
runtimeClasspath += main.output | ||
srcDir file('src/scenario/kotlin') | ||
} | ||
resources.srcDir file('src/scenario/resources') | ||
} | ||
} | ||
|
||
configurations { | ||
scenarioCompile.extendsFrom testCompile | ||
scenarioRuntime.extendsFrom testRuntime | ||
} | ||
|
||
dependencies { | ||
|
||
// Library | ||
|
||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" | ||
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" | ||
|
||
compile("com.github.corda.crash:crash.shell:$crash_version") { | ||
exclude group: "org.slf4j", module: "slf4j-jdk14" | ||
exclude group: "org.bouncycastle" | ||
} | ||
|
||
compile("com.github.corda.crash:crash.connectors.ssh:$crash_version") { | ||
exclude group: "org.slf4j", module: "slf4j-jdk14" | ||
exclude group: "org.bouncycastle" | ||
} | ||
|
||
compile "org.slf4j:log4j-over-slf4j:$slf4j_version" | ||
compile "org.slf4j:jul-to-slf4j:$slf4j_version" | ||
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" | ||
compile "org.apache.logging.log4j:log4j-core:$log4j_version" | ||
|
||
compile "commons-io:commons-io:$commonsio_version" | ||
compile "commons-logging:commons-logging:$commonslogging_version" | ||
compile "com.spotify:docker-client:$docker_client_version" | ||
compile "io.reactivex:rxjava:$rxjava_version" | ||
|
||
compile project(':finance') | ||
compile project(':node-api') | ||
compile project(':client:rpc') | ||
|
||
// Unit Tests | ||
|
||
testCompile "junit:junit:$junit_version" | ||
testCompile "org.assertj:assertj-core:$assertj_version" | ||
|
||
// Scenarios / End-to-End Tests | ||
|
||
scenarioCompile "info.cukes:cucumber-java8:$cucumber_version" | ||
scenarioCompile "info.cukes:cucumber-junit:$cucumber_version" | ||
scenarioCompile "info.cukes:cucumber-picocontainer:$cucumber_version" | ||
|
||
} | ||
|
||
compileKotlin { | ||
kotlinOptions.jvmTarget = "1.8" | ||
} | ||
|
||
compileTestKotlin { | ||
kotlinOptions.jvmTarget = "1.8" | ||
} | ||
|
||
compileScenarioKotlin { | ||
kotlinOptions.jvmTarget = "1.8" | ||
} | ||
|
||
test { | ||
testLogging.showStandardStreams = true | ||
} | ||
|
||
task scenarios(type: Test) { | ||
setTestClassesDirs sourceSets.scenario.output.getClassesDirs() | ||
classpath = sourceSets.scenario.runtimeClasspath | ||
outputs.upToDateWhen { false } | ||
|
||
if (project.hasProperty("tags")) { | ||
systemProperty "cucumber.options", "--tags $tags" | ||
logger.warn("Only running tests tagged with: $tags ...") | ||
} | ||
} | ||
|
||
//scenarios.mustRunAfter test | ||
//scenarios.dependsOn test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
*.jar |
Empty file.
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
Download and store database drivers here; for example: | ||
- h2-1.4.196.jar | ||
- mssql-jdbc-6.2.2.jre8.jar |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
#!/bin/bash | ||
|
||
VERSION=3.0.0 | ||
|
||
# Set up directories | ||
mkdir -p deps/corda/${VERSION}/apps | ||
mkdir -p deps/drivers | ||
|
||
# Copy Corda capsule into deps | ||
cp -v $(ls ../../node/capsule/build/libs/corda-*.jar | tail -n1) deps/corda/${VERSION}/corda.jar | ||
|
||
# Download database drivers | ||
curl "https://search.maven.org/remotecontent?filepath=com/h2database/h2/1.4.196/h2-1.4.196.jar" > deps/drivers/h2-1.4.196.jar | ||
curl -L "https://github.com/Microsoft/mssql-jdbc/releases/download/v6.2.2/mssql-jdbc-6.2.2.jre8.jar" > deps/drivers/mssql-jdbc-6.2.2.jre8.jar | ||
|
||
# Build required artefacts | ||
cd ../.. | ||
./gradlew buildBootstrapperJar | ||
./gradlew :finance:jar | ||
|
||
# Copy build artefacts into deps | ||
cd experimental/behave | ||
cp -v $(ls ../../tools/bootstrapper/build/libs/*.jar | tail -n1) deps/corda/${VERSION}/network-bootstrapper.jar | ||
cp -v $(ls ../../finance/build/libs/corda-finance-*.jar | tail -n1) deps/corda/${VERSION}/apps/corda-finance.jar |
28 changes: 28 additions & 0 deletions
28
experimental/behave/src/main/kotlin/net/corda/behave/Utilities.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package net.corda.behave | ||
|
||
import java.time.Duration | ||
import java.util.concurrent.CountDownLatch | ||
|
||
val Int.millisecond: Duration | ||
get() = Duration.ofMillis(this.toLong()) | ||
|
||
val Int.milliseconds: Duration | ||
get() = Duration.ofMillis(this.toLong()) | ||
|
||
val Int.second: Duration | ||
get() = Duration.ofSeconds(this.toLong()) | ||
|
||
val Int.seconds: Duration | ||
get() = Duration.ofSeconds(this.toLong()) | ||
|
||
val Int.minute: Duration | ||
get() = Duration.ofMinutes(this.toLong()) | ||
|
||
val Int.minutes: Duration | ||
get() = Duration.ofMinutes(this.toLong()) | ||
|
||
fun CountDownLatch.await(duration: Duration) = | ||
this.await(duration.toMillis(), java.util.concurrent.TimeUnit.MILLISECONDS) | ||
|
||
fun Process.waitFor(duration: Duration) = | ||
this.waitFor(duration.toMillis(), java.util.concurrent.TimeUnit.MILLISECONDS) |
13 changes: 13 additions & 0 deletions
13
...imental/behave/src/main/kotlin/net/corda/behave/database/DatabaseConfigurationTemplate.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package net.corda.behave.database | ||
|
||
import net.corda.behave.node.configuration.DatabaseConfiguration | ||
|
||
open class DatabaseConfigurationTemplate { | ||
|
||
open val connectionString: (DatabaseConfiguration) -> String = { "" } | ||
|
||
protected open val config: (DatabaseConfiguration) -> String = { "" } | ||
|
||
fun generate(config: DatabaseConfiguration) = config(config).trimMargin() | ||
|
||
} |
85 changes: 85 additions & 0 deletions
85
experimental/behave/src/main/kotlin/net/corda/behave/database/DatabaseConnection.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package net.corda.behave.database | ||
|
||
import net.corda.behave.node.configuration.DatabaseConfiguration | ||
import java.io.Closeable | ||
import java.sql.* | ||
import java.util.* | ||
|
||
class DatabaseConnection( | ||
private val config: DatabaseConfiguration, | ||
template: DatabaseConfigurationTemplate | ||
) : Closeable { | ||
|
||
private val connectionString = template.connectionString(config) | ||
|
||
private var conn: Connection? = null | ||
|
||
fun open(): Connection { | ||
try { | ||
val connectionProps = Properties() | ||
connectionProps.put("user", config.username) | ||
connectionProps.put("password", config.password) | ||
retry (5) { | ||
conn = DriverManager.getConnection(connectionString, connectionProps) | ||
} | ||
return conn ?: throw Exception("Unable to open connection") | ||
} catch (ex: SQLException) { | ||
throw Exception("An error occurred whilst connecting to \"$connectionString\". " + | ||
"Maybe the user and/or password is invalid?", ex) | ||
} | ||
} | ||
|
||
override fun close() { | ||
val connection = conn | ||
if (connection != null) { | ||
try { | ||
conn = null | ||
connection.close() | ||
} catch (ex: SQLException) { | ||
throw Exception("Failed to close database connection to \"$connectionString\"", ex) | ||
} | ||
} | ||
} | ||
|
||
private fun query(conn: Connection?, stmt: String? = null) { | ||
var statement: Statement? = null | ||
val resultset: ResultSet? | ||
try { | ||
statement = conn?.prepareStatement(stmt | ||
?: "SELECT name FROM sys.tables WHERE name = ?") | ||
statement?.setString(1, "Test") | ||
resultset = statement?.executeQuery() | ||
|
||
try { | ||
while (resultset?.next() == true) { | ||
val name = resultset.getString("name") | ||
println(name) | ||
} | ||
} catch (e: Exception) { | ||
e.printStackTrace() | ||
} finally { | ||
resultset?.close() | ||
} | ||
} catch (e: Exception) { | ||
e.printStackTrace() | ||
} finally { | ||
statement?.close() | ||
} | ||
} | ||
|
||
private fun retry(numberOfTimes: Int, action: () -> Unit) { | ||
var i = numberOfTimes | ||
while (numberOfTimes > 0) { | ||
Thread.sleep(2000) | ||
try { | ||
action() | ||
} catch (ex: Exception) { | ||
if (i == 1) { | ||
throw ex | ||
} | ||
} | ||
i -= 1 | ||
} | ||
} | ||
|
||
} |
Oops, something went wrong.