Corda Behave: compendium of minor changes and improvements (#3008)

* Compendium of minor changes and improvements:
- build fat behave-jar so can run scenarios from shell scripts (from TC)
- additional run script to execute basic scenarios (for TC)
- default staging path shortened to "corda" (removed deps)
- toned down logging (info -> debug)
- fixed all compiler warnings
- fixed couple of bugs in startup checking steps
- base scenarios use variables declared using Examples parameterization

* Added missing braces

* Changes to address PR feedback.

* Mark underlying Cucumber libraries for future de-coupling.
This commit is contained in:
josecoll
2018-04-26 16:13:34 +01:00
committed by GitHub
parent 22d5967b9e
commit b208d03f5c
15 changed files with 119 additions and 66 deletions

1
.idea/compiler.xml generated
View File

@ -10,6 +10,7 @@
<module name="bank-of-corda-demo_integrationTest" target="1.8" /> <module name="bank-of-corda-demo_integrationTest" target="1.8" />
<module name="bank-of-corda-demo_main" target="1.8" /> <module name="bank-of-corda-demo_main" target="1.8" />
<module name="bank-of-corda-demo_test" target="1.8" /> <module name="bank-of-corda-demo_test" target="1.8" />
<module name="behave_behave" target="1.8" />
<module name="behave_main" target="1.8" /> <module name="behave_main" target="1.8" />
<module name="behave_scenario" target="1.8" /> <module name="behave_scenario" target="1.8" />
<module name="behave_test" target="1.8" /> <module name="behave_test" target="1.8" />

View File

@ -29,19 +29,19 @@ repositories {
} }
sourceSets { sourceSets {
scenario { behave {
java { java {
compileClasspath += main.output compileClasspath += main.output
runtimeClasspath += main.output runtimeClasspath += main.output
srcDir file('src/scenario/kotlin') srcDirs = ["src/main/kotlin", "src/scenario/kotlin"]
} }
resources.srcDir file('src/scenario/resources') resources.srcDir file('src/scenario/resources')
} }
} }
configurations { configurations {
scenarioCompile.extendsFrom testCompile behaveCompile.extendsFrom testCompile
scenarioRuntime.extendsFrom testRuntime behaveRuntime.extendsFrom testRuntime
} }
dependencies { dependencies {
@ -88,9 +88,9 @@ dependencies {
// Scenarios / End-to-End Tests // Scenarios / End-to-End Tests
scenarioCompile "info.cukes:cucumber-java8:$cucumber_version" behaveCompile "info.cukes:cucumber-java8:$cucumber_version"
scenarioCompile "info.cukes:cucumber-junit:$cucumber_version" behaveCompile "info.cukes:cucumber-junit:$cucumber_version"
scenarioCompile "info.cukes:cucumber-picocontainer:$cucumber_version" behaveCompile "info.cukes:cucumber-picocontainer:$cucumber_version"
} }
compileKotlin { compileKotlin {
@ -101,24 +101,25 @@ compileTestKotlin {
kotlinOptions.jvmTarget = "1.8" kotlinOptions.jvmTarget = "1.8"
} }
compileScenarioKotlin {
kotlinOptions.jvmTarget = "1.8"
}
test { test {
testLogging.showStandardStreams = true testLogging.showStandardStreams = true
} }
task scenarios(type: Test) { task behaveJar(type: Jar) {
setTestClassesDirs sourceSets.scenario.output.getClassesDirs() baseName "corda-behave"
classpath = sourceSets.scenario.runtimeClasspath from sourceSets.behave.output
outputs.upToDateWhen { false } from {
configurations.behaveCompile.collect {
if (project.hasProperty("tags")) { it.isDirectory() ? it : zipTree(it)
systemProperty "cucumber.options", "--tags $tags" }
logger.warn("Only running tests tagged with: $tags ...") }
zip64 true
exclude("features/**")
exclude("scripts/**")
exclude("META-INF/*.DSA")
exclude("META-INF/*.RSA")
exclude("META-INF/*.SF")
manifest {
attributes 'Main-Class': 'net.corda.behave.scenarios.ScenarioRunner'
} }
} }
//scenarios.mustRunAfter test
//scenarios.dependsOn test

View File

@ -6,26 +6,30 @@ set -x
# For example: # For example:
# corda-master => git clone https://github.com/corda/corda # corda-master => git clone https://github.com/corda/corda
# r3corda-master => git clone https://github.com/corda/enterprise # r3corda-master => git clone https://github.com/corda/enterprise
VERSION=corda-3.0 VERSION=corda-master
STAGING_DIR=deps/corda/${VERSION} STAGING_DIR=~/staging
DRIVERS_DIR=deps/drivers CORDA_DIR=${STAGING_DIR}/corda/${VERSION}
CORDAPP_DIR=${CORDA_DIR}/apps
DRIVERS_DIR=${STAGING_DIR}/drivers
# Set up directories # Set up directories
mkdir -p ${STAGING_DIR}/apps mkdir -p ${STAGING_DIR}
mkdir -p ${CORDA_DIR}
mkdir -p ${CORDAPP_DIR}
mkdir -p ${DRIVERS_DIR} mkdir -p ${DRIVERS_DIR}
# Copy Corda capsule into deps # Copy Corda capsule into deps
cd ../.. cd ../..
./gradlew clean :node:capsule:buildCordaJar :finance:jar ./gradlew clean :node:capsule:buildCordaJar :finance:jar
cp -v $(ls node/capsule/build/libs/corda-*.jar | tail -n1) experimental/behave/${STAGING_DIR}/corda.jar cp -v $(ls node/capsule/build/libs/corda-*.jar | tail -n1) ${CORDA_DIR}/corda.jar
# Copy finance library # Copy finance library
cp -v $(ls finance/build/libs/corda-finance-*.jar | tail -n1) experimental/behave/${STAGING_DIR}/apps cp -v $(ls finance/build/libs/corda-finance-*.jar | tail -n1) ${CORDAPP_DIR}
# Download database drivers # Download database drivers
curl "https://search.maven.org/remotecontent?filepath=com/h2database/h2/1.4.196/h2-1.4.196.jar" > experimental/behave/${DRIVERS_DIR}/h2-1.4.196.jar curl "https://search.maven.org/remotecontent?filepath=com/h2database/h2/1.4.196/h2-1.4.196.jar" > ${DRIVERS_DIR}/h2-1.4.196.jar
curl -L "http://central.maven.org/maven2/org/postgresql/postgresql/42.1.4/postgresql-42.1.4.jar" > experimental/behave/${DRIVERS_DIR}/postgresql-42.1.4.jar curl -L "http://central.maven.org/maven2/org/postgresql/postgresql/42.1.4/postgresql-42.1.4.jar" > ${DRIVERS_DIR}/postgresql-42.1.4.jar
# Build Network Bootstrapper # Build Network Bootstrapper
./gradlew buildBootstrapperJar ./gradlew buildBootstrapperJar
cp -v $(ls tools/bootstrapper/build/libs/*.jar | tail -n1) experimental/behave/${STAGING_DIR}/network-bootstrapper.jar cp -v $(ls tools/bootstrapper/build/libs/*.jar | tail -n1) ${CORDA_DIR}/network-bootstrapper.jar

View File

@ -93,11 +93,8 @@ class Network private constructor(
fun copyDatabaseDrivers() { fun copyDatabaseDrivers() {
val driverDirectory = (targetDirectory / "libs").createDirectories() val driverDirectory = (targetDirectory / "libs").createDirectories()
log.info("Copying database drivers from $stagingRoot/deps/drivers to $driverDirectory") log.info("Copying database drivers from $stagingRoot/drivers to $driverDirectory")
FileUtils.copyDirectory( FileUtils.copyDirectory((stagingRoot / "drivers").toFile(), driverDirectory.toFile())
(stagingRoot / "deps" / "drivers").toFile(),
driverDirectory.toFile()
)
} }
fun configureNodes(): Boolean { fun configureNodes(): Boolean {

View File

@ -85,7 +85,7 @@ class Distribution private constructor(
private val distributions = mutableListOf<Distribution>() private val distributions = mutableListOf<Distribution>()
private val nodePrefix = stagingRoot / "deps/corda" private val nodePrefix = stagingRoot / "corda"
val MASTER = fromJarFile("corda-master") val MASTER = fromJarFile("corda-master")

View File

@ -220,7 +220,7 @@ class Node(
private fun installApps() { private fun installApps() {
val version = config.distribution.version val version = config.distribution.version
val appDirectory = stagingRoot / "deps" / "corda" / version / "apps" val appDirectory = stagingRoot / "corda" / version / "apps"
if (appDirectory.exists()) { if (appDirectory.exists()) {
val targetAppDirectory = runtimeDirectory / "cordapps" val targetAppDirectory = runtimeDirectory / "cordapps"
FileUtils.copyDirectory(appDirectory.toFile(), targetAppDirectory.toFile()) FileUtils.copyDirectory(appDirectory.toFile(), targetAppDirectory.toFile())

View File

@ -45,7 +45,7 @@ class Configuration(
fun writeToFile(file: Path) { fun writeToFile(file: Path) {
file.writeText(this.generate()) file.writeText(this.generate())
log.info(this.generate()) log.debug(this.generate())
} }
private fun generate() = listOf(basicConfig, database.config(), extraConfig) private fun generate() = listOf(basicConfig, database.config(), extraConfig)

View File

@ -1,12 +1,11 @@
package net.corda.behave.process package net.corda.behave.process
import java.io.File
import java.nio.file.Path import java.nio.file.Path
import java.time.Duration import java.time.Duration
class JarCommand( class JarCommand(
jarFile: Path, jarFile: Path,
arguments: Array<String>, arguments: Array<out String>,
directory: Path, directory: Path,
timeout: Duration, timeout: Duration,
enableRemoteDebugging: Boolean = false enableRemoteDebugging: Boolean = false

View File

@ -17,9 +17,6 @@ class Cash(state: ScenarioState) : Substeps(state) {
fun numberOfIssuableCurrencies(nodeName: String): Int { fun numberOfIssuableCurrencies(nodeName: String): Int {
return withClient(nodeName) { return withClient(nodeName) {
for (flow in it.registeredFlows()) {
log.info(flow)
}
try { try {
val config = it.startFlow(::CashConfigDataFlow).returnValue.get(10, TimeUnit.SECONDS) val config = it.startFlow(::CashConfigDataFlow).returnValue.get(10, TimeUnit.SECONDS)
for (supportedCurrency in config.supportedCurrencies) { for (supportedCurrency in config.supportedCurrencies) {

View File

@ -10,7 +10,7 @@ class Startup(state: ScenarioState) : Substeps(state) {
fun hasLoggingInformation(nodeName: String) { fun hasLoggingInformation(nodeName: String) {
withNetwork { withNetwork {
log.info("Retrieving logging information for node '$nodeName' ...") log.info("Retrieving logging information for node '$nodeName' ...")
if (!node(nodeName).nodeInfoGenerationOutput.find("Logs can be found in.*").any()) { if (!node(nodeName).logOutput.find("Logs can be found in.*").any()) {
fail("Unable to find logging information for node $nodeName") fail("Unable to find logging information for node $nodeName")
} }
@ -26,7 +26,7 @@ class Startup(state: ScenarioState) : Substeps(state) {
fun hasDatabaseDetails(nodeName: String) { fun hasDatabaseDetails(nodeName: String) {
withNetwork { withNetwork {
log.info("Retrieving database details for node '$nodeName' ...") log.info("Retrieving database details for node '$nodeName' ...")
if (!node(nodeName).nodeInfoGenerationOutput.find("Database connection url is.*").any()) { if (!node(nodeName).logOutput.find("Database connection url is.*").any()) {
fail("Unable to find database details for node $nodeName") fail("Unable to find database details for node $nodeName")
} }
} }
@ -104,7 +104,7 @@ class Startup(state: ScenarioState) : Substeps(state) {
val cordappDirectory = node(nodeName).config.distribution.cordappDirectory val cordappDirectory = node(nodeName).config.distribution.cordappDirectory
val cordappJar = cordappDirectory / "$cordapp.jar" val cordappJar = cordappDirectory / "$cordapp.jar"
// Execute // Execute
val command = JarCommand(cordappJar, args as Array<String>, cordappDirectory, 1.minutes) val command = JarCommand(cordappJar, args, cordappDirectory, 1.minutes)
command.start() command.start()
if (!command.waitFor()) if (!command.waitFor())
fail("Failed to successfully run the CorDapp jar: $cordaApp") fail("Failed to successfully run the CorDapp jar: $cordaApp")

View File

@ -21,6 +21,7 @@ class VaultSteps : StepsBlock {
Then<String, Int, String>("^node (\\w+) vault contains (\\d+) (\\w+) states$") { node, count, contractType -> Then<String, Int, String>("^node (\\w+) vault contains (\\d+) (\\w+) states$") { node, count, contractType ->
try { try {
@Suppress("UNCHECKED_CAST")
val contractStateTypeClass = Class.forName(contractType) as Class<ContractState> val contractStateTypeClass = Class.forName(contractType) as Class<ContractState>
if (vault.query(node, contractStateTypeClass).size == count) if (vault.query(node, contractStateTypeClass).size == count)
succeed() succeed()
@ -33,7 +34,7 @@ class VaultSteps : StepsBlock {
Then<String, Long, String>("^node (\\w+) vault contains total cash of (\\d+) (\\w+)$") { node, total, currency -> Then<String, Long, String>("^node (\\w+) vault contains total cash of (\\d+) (\\w+)$") { node, total, currency ->
val cashStates = vault.query(node, Cash.State::class.java) val cashStates = vault.query(node, Cash.State::class.java)
val sumCashStates = cashStates.filter { it.state.data.amount.token.product.currencyCode == currency }?.sumByLong { it.state.data.amount.quantity } val sumCashStates = cashStates.filter { it.state.data.amount.token.product.currencyCode == currency }.sumByLong { it.state.data.amount.quantity }
print((sumCashStates)) print((sumCashStates))
if (sumCashStates == total) if (sumCashStates == total)
succeed() succeed()

View File

@ -2,22 +2,34 @@
Feature: Cash - Issuable Currencies Feature: Cash - Issuable Currencies
To have cash on ledger, certain nodes must have the ability to issue cash of various currencies. To have cash on ledger, certain nodes must have the ability to issue cash of various currencies.
Scenario: Node can issue no currencies by default Scenario Outline: Node can issue no currencies by default
Given a node PartyA of version master Given a node PartyA of version <Node-Version>
And node PartyA has the finance app installed And node PartyA has the finance app installed
When the network is ready When the network is ready
Then node PartyA has 0 issuable currencies Then node PartyA has 0 issuable currencies
Scenario: Node has an issuable currency Examples:
Given a node PartyA of version master | Node-Version |
| master |
Scenario Outline: Node has an issuable currency
Given a node PartyA of version <Node-Version>
And node PartyA can issue currencies of denomination USD And node PartyA can issue currencies of denomination USD
And node PartyA has the finance app installed And node PartyA has the finance app installed
When the network is ready When the network is ready
Then node PartyA has 1 issuable currency Then node PartyA has 1 issuable currency
Scenario: Node can issue a currency Examples:
Given a node PartyA of version master | Node-Version |
And a nonvalidating notary Notary of version master | master |
Scenario Outline: Node can issue a currency
Given a node PartyA of version <Node-Version>
And a nonvalidating notary Notary of version <Node-Version>
And node PartyA has the finance app installed And node PartyA has the finance app installed
When the network is ready When the network is ready
Then node PartyA can issue 100 USD Then node PartyA can issue 100 USD
Examples:
| Node-Version |
| master |

View File

@ -11,4 +11,6 @@ Feature: Database - Connection
Examples: Examples:
| Node-Version | Database-Type | | Node-Version | Database-Type |
| master | H2 | | master | H2 |
# To run this scenario using postgreSQL you must ensure that Docker is running locally
# | master | postgreSQL | # | master | postgreSQL |

View File

@ -3,19 +3,32 @@ Feature: Startup Information - Logging
A Corda node should inform the user of important parameters during startup so that he/she can confirm the setup and A Corda node should inform the user of important parameters during startup so that he/she can confirm the setup and
configure / connect relevant software to said node. configure / connect relevant software to said node.
Scenario: Node shows logging information on startup Scenario Outline: Node shows logging information on startup
Given a node PartyA of version master Given a node PartyA of version <Node-Version>
And node PartyA uses database of type H2 And node PartyA uses database of type <Database-Type>
And node PartyA is located in London, GB And node PartyA is located in London, GB
When the network is ready When the network is ready
Then user can retrieve logging information for node PartyA Then user can retrieve logging information for node PartyA
Scenario: Node shows database details on startup Examples:
Given a node PartyA of version master | Node-Version | Database-Type |
| master | H2 |
Scenario Outline: Node shows database details on startup
Given a node PartyA of version <Node-Version>
And node PartyA uses database of type <Database-Type>
When the network is ready When the network is ready
Then user can retrieve database details for node PartyA Then user can retrieve database details for node PartyA
Scenario: Node shows version information on startup Examples:
Given a node PartyA of version master | Node-Version | Database-Type |
Then node PartyA is on platform version 4 | master | H2 |
And node PartyA is on release version corda-4.0-snapshot
Scenario Outline: Node shows version information on startup
Given a node PartyA of version <Node-Version>
Then node PartyA is on platform version <Platform-Version>
And node PartyA is on release version <Release-Version>
Examples:
| Node-Version | Platform-Version | Release-Version |
| master | 4 | corda-4.0-snapshot |

View File

@ -0,0 +1,26 @@
#!/bin/bash
#
# Run this script from the experimental/behave directory
#
# $ pwd
# ./IdeaProjects/corda-reviews/experimental/behave
# $ src/scenario/resources/scripts/run-behave-features.sh
#
# Note: please ensure you have configured your staging environment by running the top-level script: prepare.sh
BUILD_DIR=$PWD
cd ${BUILD_DIR}
../../gradlew behaveJar
BEHAVE_JAR=$(ls build/libs/corda-behave-*.jar | tail -n1)
STAGING_ROOT=~/staging
# startup
java -DSTAGING_ROOT=${STAGING_ROOT} -jar ${BEHAVE_JAR} --glue net.corda.behave.scenarios -path ./src/scenario/resources/features/startup/logging.feature
# cash
java -DSTAGING_ROOT=${STAGING_ROOT} -jar ${BEHAVE_JAR} --glue net.corda.behave.scenarios -path ./src/scenario/resources/features/cash/currencies.feature
# database
java -DSTAGING_ROOT=${STAGING_ROOT} -jar ${BEHAVE_JAR} --glue net.corda.behave.scenarios -path ./src/scenario/resources/features/cash/currencies.feature