mirror of
https://github.com/corda/corda.git
synced 2025-04-07 19:34:41 +00:00
Merged in aslemmer-integtest-docs (pull request #553)
docs: Add integration test tutorial
This commit is contained in:
commit
ba84ce7358
@ -19,17 +19,31 @@ repositories {
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
integrationTestCompile.extendsFrom testCompile
|
||||
integrationTestRuntime.extendsFrom testRuntime
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
resources {
|
||||
srcDir "../../../config/dev"
|
||||
}
|
||||
}
|
||||
|
||||
integrationTest {
|
||||
kotlin {
|
||||
compileClasspath += main.output + test.output
|
||||
runtimeClasspath += main.output + test.output
|
||||
srcDir file('src/integration-test/kotlin')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':core')
|
||||
compile project(':client')
|
||||
compile project(':test-utils')
|
||||
|
||||
compile "org.graphstream:gs-core:1.3"
|
||||
compile("org.graphstream:gs-ui:1.3") {
|
||||
@ -52,3 +66,8 @@ applicationDistribution.into("bin") {
|
||||
from(getClientRpcTutorial)
|
||||
fileMode = 0755
|
||||
}
|
||||
|
||||
task integrationTest(type: Test) {
|
||||
testClassesDir = sourceSets.integrationTest.output.classesDir
|
||||
classpath = sourceSets.integrationTest.runtimeClasspath
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
package net.corda.docs
|
||||
|
||||
import net.corda.client.CordaRPCClient
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.core.contracts.DOLLARS
|
||||
import net.corda.core.contracts.issuedBy
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.serialization.OpaqueBytes
|
||||
import net.corda.flows.CashCommand
|
||||
import net.corda.flows.CashFlow
|
||||
import net.corda.flows.CashFlowResult
|
||||
import net.corda.node.driver.driver
|
||||
import net.corda.node.services.User
|
||||
import net.corda.node.services.config.configureTestSSL
|
||||
import net.corda.node.services.messaging.ArtemisMessagingComponent
|
||||
import net.corda.node.services.messaging.startFlow
|
||||
import net.corda.node.services.startFlowPermission
|
||||
import net.corda.node.services.transactions.ValidatingNotaryService
|
||||
import net.corda.testing.expect
|
||||
import net.corda.testing.expectEvents
|
||||
import net.corda.testing.parallel
|
||||
import net.corda.testing.sequence
|
||||
import org.junit.Test
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class IntegrationTestingTutorial {
|
||||
|
||||
@Test
|
||||
fun aliceBobCashExchangeExample() {
|
||||
// START 1
|
||||
driver {
|
||||
val testUser = User("testUser", "testPassword", permissions = setOf(startFlowPermission<CashFlow>()))
|
||||
val aliceFuture = startNode("Alice", rpcUsers = listOf(testUser))
|
||||
val bobFuture = startNode("Bob", rpcUsers = listOf(testUser))
|
||||
val notaryFuture = startNode("Notary", advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type)))
|
||||
val alice = aliceFuture.get()
|
||||
val bob = bobFuture.get()
|
||||
val notary = notaryFuture.get()
|
||||
// END 1
|
||||
|
||||
// START 2
|
||||
val aliceClient = CordaRPCClient(
|
||||
host = ArtemisMessagingComponent.toHostAndPort(alice.nodeInfo.address),
|
||||
config = configureTestSSL()
|
||||
)
|
||||
aliceClient.start("testUser", "testPassword")
|
||||
val aliceProxy = aliceClient.proxy()
|
||||
val bobClient = CordaRPCClient(
|
||||
host = ArtemisMessagingComponent.toHostAndPort(bob.nodeInfo.address),
|
||||
config = configureTestSSL()
|
||||
)
|
||||
bobClient.start("testUser", "testPassword")
|
||||
val bobProxy = bobClient.proxy()
|
||||
// END 2
|
||||
|
||||
// START 3
|
||||
val bobVaultUpdates = bobProxy.vaultAndUpdates().second
|
||||
val aliceVaultUpdates = aliceProxy.vaultAndUpdates().second
|
||||
// END 3
|
||||
|
||||
// START 4
|
||||
val issueRef = OpaqueBytes.of(0)
|
||||
for (i in 1 .. 10) {
|
||||
thread {
|
||||
aliceProxy.startFlow(::CashFlow, CashCommand.IssueCash(
|
||||
amount = i.DOLLARS,
|
||||
issueRef = issueRef,
|
||||
recipient = bob.nodeInfo.legalIdentity,
|
||||
notary = notary.nodeInfo.notaryIdentity
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
bobVaultUpdates.expectEvents {
|
||||
parallel(
|
||||
(1 .. 10).map { i ->
|
||||
expect(
|
||||
match = { update: Vault.Update ->
|
||||
(update.produced.first().state.data as Cash.State).amount.quantity == i * 100L
|
||||
}
|
||||
) { update ->
|
||||
println("Bob vault update of $update")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
// END 4
|
||||
|
||||
// START 5
|
||||
for (i in 1 .. 10) {
|
||||
val flowHandle = bobProxy.startFlow(::CashFlow, CashCommand.PayCash(
|
||||
amount = i.DOLLARS.issuedBy(alice.nodeInfo.legalIdentity.ref(issueRef)),
|
||||
recipient = alice.nodeInfo.legalIdentity
|
||||
))
|
||||
assert(flowHandle.returnValue.toBlocking().first() is CashFlowResult.Success)
|
||||
}
|
||||
|
||||
aliceVaultUpdates.expectEvents {
|
||||
sequence(
|
||||
(1 .. 10).map { i ->
|
||||
expect { update: Vault.Update ->
|
||||
println("Alice got vault update of $update")
|
||||
assertEquals((update.produced.first().state.data as Cash.State).amount.quantity, i * 100L)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// END 5
|
@ -68,6 +68,7 @@ Read on to learn:
|
||||
tutorial-contract
|
||||
tutorial-contract-clauses
|
||||
tutorial-test-dsl
|
||||
tutorial-integration-testing
|
||||
tutorial-clientrpc-api
|
||||
flow-state-machines
|
||||
flow-testing
|
||||
@ -96,6 +97,7 @@ Read on to learn:
|
||||
:caption: Appendix
|
||||
|
||||
loadtesting
|
||||
setting-up-a-corda-network
|
||||
secure-coding-guidelines
|
||||
release-process
|
||||
release-notes
|
||||
|
117
docs/source/tutorial-integration-testing.rst
Normal file
117
docs/source/tutorial-integration-testing.rst
Normal file
@ -0,0 +1,117 @@
|
||||
Integration Test Tutorial
|
||||
=========================
|
||||
|
||||
Integration testing involves bringing up nodes locally and testing
|
||||
invariants about them by starting flows and inspecting their state.
|
||||
|
||||
In this tutorial we will bring up three nodes Alice, Bob and a
|
||||
Notary. Alice will issue Cash to Bob, then Bob will send this Cash
|
||||
back to Alice. We will see how to test some simple deterministic and
|
||||
nondeterministic invariants in the meantime.
|
||||
|
||||
(Note that this example where Alice is self-issuing Cash is purely for
|
||||
demonstration purposes, in reality Cash would be issued by a bank and
|
||||
subsequently passed around.)
|
||||
|
||||
In order to spawn nodes we will use the Driver DSL. This DSL allows
|
||||
one to start up node processes from code. It manages a network map
|
||||
service and safe shutting down of nodes in the background.
|
||||
|
||||
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt
|
||||
:language: kotlin
|
||||
:start-after: START 1
|
||||
:end-before: END 1
|
||||
|
||||
The above code creates a ``User`` permissioned to start the
|
||||
``CashFlow`` protocol. It then starts up Alice and Bob with this user,
|
||||
allowing us to later connect to the nodes.
|
||||
|
||||
Then the notary is started up. Note that we need to add
|
||||
``ValidatingNotaryService`` as an advertised service in order for this
|
||||
node to serve notary functionality. This is also where flows added in
|
||||
plugins should be specified. Note also that we won't connect to the
|
||||
notary directly, so there's no need to pass in the test ``User``.
|
||||
|
||||
The ``startNode`` function returns a future that completes once the
|
||||
node is fully started. This allows starting of the nodes to be
|
||||
parallel. We wait on these futures as we need the information
|
||||
returned; their respective ``NodeInfo`` s.
|
||||
|
||||
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt
|
||||
:language: kotlin
|
||||
:start-after: START 2
|
||||
:end-before: END 2
|
||||
|
||||
Next we connect to Alice and Bob respectively from the test process
|
||||
using the test user we created. Then we establish RPC links that allow
|
||||
us to start flows and query state.
|
||||
|
||||
Note that Driver nodes start up with test server certificiates, so
|
||||
it's enough to pass in ``configureTestSSL()`` for the clients.
|
||||
|
||||
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt
|
||||
:language: kotlin
|
||||
:start-after: START 3
|
||||
:end-before: END 3
|
||||
|
||||
We will be interested in changes to Alice's and Bob's vault, so we
|
||||
query a stream of vault updates from each.
|
||||
|
||||
Now that we're all set up we can finally get some Cash action going!
|
||||
|
||||
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt
|
||||
:language: kotlin
|
||||
:start-after: START 4
|
||||
:end-before: END 4
|
||||
|
||||
The first loop creates 10 threads, each starting a ``CashFlow`` flow
|
||||
on the Alice node. We specify that we want to issue ``i`` dollars to
|
||||
Bob, using the Notary as the notary responsible for notarising the
|
||||
created states. Note that no notarisation will occur yet as we're not
|
||||
spending any states, only entering new ones to the ledger.
|
||||
|
||||
We started the flows from different threads for the sake of the
|
||||
tutorial, to demonstrate how to test non-determinism, which is what
|
||||
the ``expectEvents`` block does.
|
||||
|
||||
The Expect DSL allows ordering constraints to be checked on a stream
|
||||
of events. The above code specifies that we are expecting 10 updates
|
||||
to be emitted on the ``bobVaultUpdates`` stream in unspecified order
|
||||
(this is what the ``parallel`` construct does). We specify a
|
||||
(otherwise optional) ``match`` predicate to identify specific updates
|
||||
we are interested in, which we then print.
|
||||
|
||||
If we run the code written so far we should see 4 nodes starting up
|
||||
(Alice,Bob,Notary + implicit Network Map service), then 10 logs of Bob
|
||||
receiving 1,2,...10 dollars from Alice in some unspecified order.
|
||||
|
||||
Next we want Bob to send this Cash back to Alice.
|
||||
|
||||
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt
|
||||
:language: kotlin
|
||||
:start-after: START 5
|
||||
:end-before: END 5
|
||||
|
||||
This time we'll do it sequentially. We make Bob pay 1,2,..10 dollars
|
||||
to Alice in order. We make sure that a the ``CashFlow`` has finished
|
||||
by waiting on ``startFlow`` 's ``returnValue``.
|
||||
|
||||
Then we use the Expect DSL again, this time using ``sequence`` to test
|
||||
for the updates arriving in the order we expect them to.
|
||||
|
||||
Note that ``parallel`` and ``sequence`` may be nested into each other
|
||||
arbitrarily to test more complex scenarios.
|
||||
|
||||
That's it! We saw how to start up several corda nodes locally, how to
|
||||
connect to them, and how to test some simple invariants about
|
||||
``CashFlow``.
|
||||
|
||||
To run the complete test you can open
|
||||
``example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt``
|
||||
from IntelliJ and run the test, or alternatively use gradle:
|
||||
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
# Run example-code integration tests
|
||||
./gradlew docs/source/example-code:integrationTest -i
|
@ -68,6 +68,7 @@ inline fun <reified E : Any> expect(
|
||||
* @param expectations The pieces of DSL that should run sequentially when events arrive.
|
||||
*/
|
||||
fun <E> sequence(vararg expectations: ExpectCompose<E>): ExpectCompose<E> = ExpectCompose.Sequential(listOf(*expectations))
|
||||
fun <E> sequence(expectations: List<ExpectCompose<E>>): ExpectCompose<E> = ExpectCompose.Sequential(expectations)
|
||||
|
||||
/**
|
||||
* Tests that events arrive in unspecified order.
|
||||
@ -75,6 +76,7 @@ fun <E> sequence(vararg expectations: ExpectCompose<E>): ExpectCompose<E> = Expe
|
||||
* @param expectations The pieces of DSL all of which should run but in an unspecified order depending on what sequence events arrive.
|
||||
*/
|
||||
fun <E> parallel(vararg expectations: ExpectCompose<E>): ExpectCompose<E> = ExpectCompose.Parallel(listOf(*expectations))
|
||||
fun <E> parallel(expectations: List<ExpectCompose<E>>): ExpectCompose<E> = ExpectCompose.Parallel(expectations)
|
||||
|
||||
/**
|
||||
* Tests that N events of the same type arrive
|
||||
|
Loading…
x
Reference in New Issue
Block a user