Skip to content

Commit

Permalink
Merge pull request corda#2500 from corda/tlil/bdd-test-framework
Browse files Browse the repository at this point in the history
Experimental - BDD Test Framework
  • Loading branch information
Tommy Lillehagen authored Feb 14, 2018
2 parents 117f4a7 + 407885c commit 3802066
Show file tree
Hide file tree
Showing 73 changed files with 3,141 additions and 1 deletion.
5 changes: 4 additions & 1 deletion .idea/compiler.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 58 additions & 0 deletions experimental/behave/README.md
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"
```
119 changes: 119 additions & 0 deletions experimental/behave/build.gradle
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
1 change: 1 addition & 0 deletions experimental/behave/deps/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.jar
Empty file.
Empty file.
Empty file.
3 changes: 3 additions & 0 deletions experimental/behave/deps/drivers/README.md
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
24 changes: 24 additions & 0 deletions experimental/behave/prepare.sh
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 experimental/behave/src/main/kotlin/net/corda/behave/Utilities.kt
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)
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()

}
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
}
}

}
Loading

0 comments on commit 3802066

Please sign in to comment.