Fixes relating to testing flows and services.

Fixed issue where Corda services installed in unit tests were not being marked as serialise as singleton. Also the driver now automatically picks up the scanning annotations. This required moving the NodeFactory used in smoke tests into a separate module.
This commit is contained in:
Shams Asari
2017-06-02 15:47:20 +01:00
parent 08cbcac40c
commit afa3efb308
23 changed files with 220 additions and 130 deletions

View File

@ -1,48 +0,0 @@
package net.corda.kotlin.rpc
import com.typesafe.config.*
import net.corda.core.crypto.commonName
import net.corda.core.identity.Party
import net.corda.nodeapi.User
class NodeConfig(
val party: Party,
val p2pPort: Int,
val rpcPort: Int,
val webPort: Int,
val extraServices: List<String>,
val users: List<User>,
var networkMap: NodeConfig? = null
) {
companion object {
val renderOptions: ConfigRenderOptions = ConfigRenderOptions.defaults().setOriginComments(false)
}
val commonName: String = party.name.commonName
/*
* The configuration object depends upon the networkMap,
* which is mutable.
*/
fun toFileConfig(): Config = ConfigFactory.empty()
.withValue("myLegalName", valueFor(party.name.toString()))
.withValue("p2pAddress", addressValueFor(p2pPort))
.withValue("extraAdvertisedServiceIds", valueFor(extraServices))
.withFallback(optional("networkMapService", networkMap, { c, n ->
c.withValue("address", addressValueFor(n.p2pPort))
.withValue("legalName", valueFor(n.party.name.toString()))
}))
.withValue("webAddress", addressValueFor(webPort))
.withValue("rpcAddress", addressValueFor(rpcPort))
.withValue("rpcUsers", valueFor(users.map(User::toMap).toList()))
.withValue("useTestClock", valueFor(true))
fun toText(): String = toFileConfig().root().render(renderOptions)
private fun <T> valueFor(any: T): ConfigValue? = ConfigValueFactory.fromAnyRef(any)
private fun addressValueFor(port: Int) = valueFor("localhost:$port")
private inline fun <T> optional(path: String, obj: T?, body: (Config, T) -> Config): Config {
val config = ConfigFactory.empty()
return if (obj == null) config else body(config, obj).atPath(path)
}
}

View File

@ -1,107 +0,0 @@
package net.corda.kotlin.rpc
import com.google.common.net.HostAndPort
import net.corda.client.rpc.CordaRPCClient
import net.corda.client.rpc.CordaRPCConnection
import net.corda.core.utilities.loggerFor
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit.SECONDS
import kotlin.test.*
class NodeProcess(
val config: NodeConfig,
val nodeDir: Path,
private val node: Process,
private val client: CordaRPCClient
) : AutoCloseable {
private companion object {
val log = loggerFor<NodeProcess>()
val javaPath: Path = Paths.get(System.getProperty("java.home"), "bin", "java")
val corda = File(this::class.java.getResource("/corda.jar").toURI())
val buildDir: Path = Paths.get(System.getProperty("build.dir"))
val capsuleDir: Path = buildDir.resolve("capsule")
}
fun connect(): CordaRPCConnection {
val user = config.users[0]
return client.start(user.username, user.password)
}
override fun close() {
log.info("Stopping node '${config.commonName}'")
node.destroy()
if (!node.waitFor(60, SECONDS)) {
log.warn("Node '${config.commonName}' has not shutdown correctly")
node.destroyForcibly()
}
log.info("Deleting Artemis directories, because they're large!")
nodeDir.resolve("artemis").toFile().deleteRecursively()
}
class Factory(val nodesDir: Path) {
init {
assertTrue(nodesDir.toFile().forceDirectory(), "Directory '$nodesDir' does not exist")
}
fun create(config: NodeConfig): NodeProcess {
val nodeDir = Files.createTempDirectory(nodesDir, config.commonName)
log.info("Node directory: {}", nodeDir)
val confFile = nodeDir.resolve("node.conf").toFile()
confFile.writeText(config.toText())
val process = startNode(nodeDir)
val client = CordaRPCClient(HostAndPort.fromParts("localhost", config.rpcPort))
val user = config.users[0]
val setupExecutor = Executors.newSingleThreadScheduledExecutor()
try {
setupExecutor.scheduleWithFixedDelay({
try {
if (!process.isAlive) {
log.error("Node '${config.commonName}' has died.")
return@scheduleWithFixedDelay
}
val conn = client.start(user.username, user.password)
conn.close()
// Cancel the "setup" task now that we've created the RPC client.
setupExecutor.shutdown()
} catch (e: Exception) {
log.warn("Node '{}' not ready yet (Error: {})", config.commonName, e.message)
}
}, 5, 1, SECONDS)
val setupOK = setupExecutor.awaitTermination(120, SECONDS)
assertTrue(setupOK && process.isAlive, "Failed to create RPC connection")
} catch (e: Exception) {
process.destroyForcibly()
throw e
} finally {
setupExecutor.shutdownNow()
}
return NodeProcess(config, nodeDir, process, client)
}
private fun startNode(nodeDir: Path): Process {
val builder = ProcessBuilder()
.command(javaPath.toString(), "-jar", corda.path)
.directory(nodeDir.toFile())
builder.environment().putAll(mapOf(
"CAPSULE_CACHE_DIR" to capsuleDir.toString()
))
return builder.start()
}
}
}
private fun File.forceDirectory(): Boolean = this.isDirectory || this.mkdirs()

View File

@ -2,17 +2,11 @@ package net.corda.kotlin.rpc
import com.google.common.hash.Hashing
import com.google.common.hash.HashingInputStream
import java.io.FilterInputStream
import java.io.InputStream
import java.nio.file.Path
import java.nio.file.Paths
import java.time.Duration.ofSeconds
import java.util.Currency
import java.util.concurrent.atomic.AtomicInteger
import kotlin.test.*
import net.corda.client.rpc.CordaRPCConnection
import net.corda.client.rpc.notUsed
import net.corda.core.contracts.*
import net.corda.core.contracts.DOLLARS
import net.corda.core.contracts.POUNDS
import net.corda.core.contracts.SWISS_FRANCS
import net.corda.core.crypto.SecureHash
import net.corda.core.getOrThrow
import net.corda.core.identity.Party
@ -20,27 +14,34 @@ import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.StateMachineUpdate
import net.corda.core.messaging.startFlow
import net.corda.core.messaging.startTrackedFlow
import net.corda.core.seconds
import net.corda.core.serialization.OpaqueBytes
import net.corda.core.sizedInputStreamAndHash
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.core.utilities.loggerFor
import net.corda.flows.CashIssueFlow
import net.corda.nodeapi.User
import net.corda.smoketesting.NodeConfig
import net.corda.smoketesting.NodeProcess
import org.apache.commons.io.output.NullOutputStream
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.io.FilterInputStream
import java.io.InputStream
import java.util.*
import java.util.concurrent.atomic.AtomicInteger
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
class StandaloneCordaRPClientTest {
private companion object {
val log = loggerFor<StandaloneCordaRPClientTest>()
val buildDir: Path = Paths.get(System.getProperty("build.dir"))
val nodesDir: Path = buildDir.resolve("nodes")
val user = User("user1", "test", permissions = setOf("ALL"))
val factory = NodeProcess.Factory(nodesDir)
val port = AtomicInteger(15000)
const val attachmentSize = 2116
const val timeout = 60L
val timeout = 60.seconds
}
private lateinit var notary: NodeProcess
@ -59,7 +60,7 @@ class StandaloneCordaRPClientTest {
@Before
fun setUp() {
notary = factory.create(notaryConfig)
notary = NodeProcess.Factory().create(notaryConfig)
connection = notary.connect()
rpcProxy = connection.proxy
notaryIdentity = fetchNotaryIdentity()
@ -91,7 +92,7 @@ class StandaloneCordaRPClientTest {
@Test
fun `test starting flow`() {
rpcProxy.startFlow(::CashIssueFlow, 127.POUNDS, OpaqueBytes.of(0), notaryIdentity, notaryIdentity)
.returnValue.getOrThrow(ofSeconds(timeout))
.returnValue.getOrThrow(timeout)
}
@Test
@ -104,7 +105,7 @@ class StandaloneCordaRPClientTest {
log.info("Flow>> $msg")
++trackCount
}
handle.returnValue.getOrThrow(ofSeconds(timeout))
handle.returnValue.getOrThrow(timeout)
assertNotEquals(0, trackCount)
}
@ -128,7 +129,7 @@ class StandaloneCordaRPClientTest {
// Now issue some cash
rpcProxy.startFlow(::CashIssueFlow, 513.SWISS_FRANCS, OpaqueBytes.of(0), notaryIdentity, notaryIdentity)
.returnValue.getOrThrow(ofSeconds(timeout))
.returnValue.getOrThrow(timeout)
assertEquals(1, updateCount)
}
@ -145,7 +146,7 @@ class StandaloneCordaRPClientTest {
// Now issue some cash
rpcProxy.startFlow(::CashIssueFlow, 629.POUNDS, OpaqueBytes.of(0), notaryIdentity, notaryIdentity)
.returnValue.getOrThrow(ofSeconds(timeout))
.returnValue.getOrThrow(timeout)
assertNotEquals(0, updateCount)
// Check that this cash exists in the vault