CORDA-2345: Simplified TestCordapp to make it inline with the recent CorDapp versioning changes (#4434)

TestCordapp has now two implementations to clearly separate the two use cases it has in the Corda repo:

* TestCordappImpl which implements the revised public API of TestCordapp; namely that a TestCordapp instance references a real CorDapp jar on the classpath. This is either an external dependency jar in which case it’s taken as is and given to the node, or it’s a local gradle project in which case it’s compiled using the gradle “jar” task to generate the CorDapp jar. This approach means the jar has all the original CorDapp versioning information, which is important that it’s correct when testing. To this end, TestCordapp only needs to expose the ability to specify the app’s config. All the remaining properties have moved to CustomCordapp.

* CustomCordapp for creating arbitrary custom CorDapps, including specifying the jar’s MANIFEST values. This is internal API and only used for testing the platform. Technically this shouldn’t implement TestCordapp but does so to reduce the complexity of the driver and mock network.
This commit is contained in:
Shams Asari 2018-12-20 09:49:58 +00:00 committed by GitHub
parent 4aaefb4fe9
commit 830959c9f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 748 additions and 633 deletions

5
.idea/compiler.xml generated
View File

@ -39,6 +39,8 @@
<module name="contracts-states_integrationTest" target="1.8" /> <module name="contracts-states_integrationTest" target="1.8" />
<module name="contracts-states_main" target="1.8" /> <module name="contracts-states_main" target="1.8" />
<module name="contracts-states_test" target="1.8" /> <module name="contracts-states_test" target="1.8" />
<module name="contracts_main" target="1.8" />
<module name="contracts_test" target="1.8" />
<module name="corda-core_integrationTest" target="1.8" /> <module name="corda-core_integrationTest" target="1.8" />
<module name="corda-core_smokeTest" target="1.8" /> <module name="corda-core_smokeTest" target="1.8" />
<module name="corda-finance_integrationTest" target="1.8" /> <module name="corda-finance_integrationTest" target="1.8" />
@ -260,6 +262,9 @@
<module name="webserver_integrationTest" target="1.8" /> <module name="webserver_integrationTest" target="1.8" />
<module name="webserver_main" target="1.8" /> <module name="webserver_main" target="1.8" />
<module name="webserver_test" target="1.8" /> <module name="webserver_test" target="1.8" />
<module name="workflows_integrationTest" target="1.8" />
<module name="workflows_main" target="1.8" />
<module name="workflows_test" target="1.8" />
</bytecodeTargetLevel> </bytecodeTargetLevel>
</component> </component>
<component name="JavacSettings"> <component name="JavacSettings">

View File

@ -18,7 +18,7 @@ import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.CHARLIE_NAME import net.corda.testing.core.CHARLIE_NAME
import net.corda.testing.core.singleIdentity import net.corda.testing.core.singleIdentity
import net.corda.testing.node.internal.FINANCE_CORDAPP import net.corda.testing.node.internal.FINANCE_CORDAPPS
import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.startFlow import net.corda.testing.node.internal.startFlow
import org.junit.After import org.junit.After
@ -35,7 +35,7 @@ class IdentitySyncFlowTests {
fun before() { fun before() {
// We run this in parallel threads to help catch any race conditions that may exist. // We run this in parallel threads to help catch any race conditions that may exist.
mockNet = InternalMockNetwork( mockNet = InternalMockNetwork(
cordappsForAllNodes = listOf(FINANCE_CORDAPP), cordappsForAllNodes = FINANCE_CORDAPPS,
networkSendManuallyPumped = false, networkSendManuallyPumped = false,
threadPerNode = true threadPerNode = true
) )

View File

@ -387,7 +387,7 @@ val Class<*>.location: URL get() = protectionDomain.codeSource.location
/** Convenience method to get the package name of a class literal. */ /** Convenience method to get the package name of a class literal. */
val KClass<*>.packageName: String get() = java.packageName val KClass<*>.packageName: String get() = java.packageName
val Class<*>.packageName: String get() = `package`.name val Class<*>.packageName: String get() = requireNotNull(`package`?.name) { "$this not defined inside a package" }
inline val Class<*>.isAbstractClass: Boolean get() = Modifier.isAbstract(modifiers) inline val Class<*>.isAbstractClass: Boolean get() = Modifier.isAbstract(modifiers)

View File

@ -14,7 +14,6 @@ import net.corda.finance.issuedBy
import net.corda.testing.core.* import net.corda.testing.core.*
import net.corda.testing.internal.matchers.flow.willReturn import net.corda.testing.internal.matchers.flow.willReturn
import net.corda.testing.internal.matchers.flow.willThrow import net.corda.testing.internal.matchers.flow.willThrow
import net.corda.testing.node.TestCordapp
import net.corda.testing.node.internal.* import net.corda.testing.node.internal.*
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatIllegalArgumentException import org.assertj.core.api.Assertions.assertThatIllegalArgumentException
@ -77,7 +76,7 @@ class FinalityFlowTests : WithFinality {
@Test @Test
fun `allow use of the old API if the CorDapp target version is 3`() { fun `allow use of the old API if the CorDapp target version is 3`() {
// We need Bob to load at least one old CorDapp so that its FinalityHandler is enabled // We need Bob to load at least one old CorDapp so that its FinalityHandler is enabled
val bob = createBob(cordapps = listOf(cordappForPackages("com.template").withTargetVersion(3))) val bob = createBob(cordapps = listOf(cordappWithPackages("com.template").copy(targetPlatformVersion = 3)))
val stx = aliceNode.issuesCashTo(bob) val stx = aliceNode.issuesCashTo(bob)
val resultFuture = CordappResolver.withCordapp(targetPlatformVersion = 3) { val resultFuture = CordappResolver.withCordapp(targetPlatformVersion = 3) {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@ -87,7 +86,7 @@ class FinalityFlowTests : WithFinality {
assertThat(bob.services.validatedTransactions.getTransaction(stx.id)).isNotNull() assertThat(bob.services.validatedTransactions.getTransaction(stx.id)).isNotNull()
} }
private fun createBob(cordapps: List<TestCordapp> = emptyList()): TestStartedNode { private fun createBob(cordapps: List<TestCordappInternal> = emptyList()): TestStartedNode {
return mockNet.createNode(InternalMockNodeParameters(legalName = BOB_NAME, additionalCordapps = cordapps)) return mockNet.createNode(InternalMockNodeParameters(legalName = BOB_NAME, additionalCordapps = cordapps))
} }

View File

@ -34,6 +34,15 @@ Unreleased
This allows Corda 4 signed CorDapps using signature constraints to consume existing hash constrained states generated This allows Corda 4 signed CorDapps using signature constraints to consume existing hash constrained states generated
by unsigned CorDapps in previous versions of Corda. by unsigned CorDapps in previous versions of Corda.
* You can now load different CorDapps for different nodes in the node-driver and mock-network. This previously wasn't possible with the
``DriverParameters.extraCordappPackagesToScan`` and ``MockNetwork.cordappPackages`` parameters as all the nodes would get the same CorDapps.
See ``TestCordapp``, ``NodeParameters.additionalCordapps`` and ``MockNodeParameters.additionalCordapps``.
* ``DriverParameters.extraCordappPackagesToScan`` and ``MockNetwork.cordappPackages`` have been deprecated as they do not support the new
CorDapp versioning and MANIFEST metadata support that has been added. They create artificial CorDapp jars which do not preserve these
settings and thus may produce incorrect results when testing. It is recommended ``DriverParameters.cordappsForAllNodes`` and
``MockNetworkParameters.cordappsForAllNodes`` be used instead.
* Fixed a problem with IRS demo not being able to simulate future dates as expected (https://github.com/corda/corda/issues/3851). * Fixed a problem with IRS demo not being able to simulate future dates as expected (https://github.com/corda/corda/issues/3851).
* Fixed a problem that was preventing `Cash.generateSpend` to be used more than once per transaction (https://github.com/corda/corda/issues/4110). * Fixed a problem that was preventing `Cash.generateSpend` to be used more than once per transaction (https://github.com/corda/corda/issues/4110).
@ -136,9 +145,6 @@ Unreleased
* "app", "rpc", "p2p" and "unknown" are no longer allowed as uploader values when importing attachments. These are used * "app", "rpc", "p2p" and "unknown" are no longer allowed as uploader values when importing attachments. These are used
internally in security sensitive code. internally in security sensitive code.
* Introduced ``TestCorDapp`` and utilities to support asymmetric setups for nodes through ``DriverDSL``, ``MockNetwork``
and ``MockServices``.
* Change type of the ``checkpoint_value`` column. Please check the upgrade-notes on how to update your database. * Change type of the ``checkpoint_value`` column. Please check the upgrade-notes on how to update your database.
* Removed buggy :serverNameTablePrefix: configuration. * Removed buggy :serverNameTablePrefix: configuration.

View File

@ -32,6 +32,7 @@ import static net.corda.testing.core.ExpectKt.expectEvents;
import static net.corda.testing.core.TestConstants.ALICE_NAME; import static net.corda.testing.core.TestConstants.ALICE_NAME;
import static net.corda.testing.core.TestConstants.BOB_NAME; import static net.corda.testing.core.TestConstants.BOB_NAME;
import static net.corda.testing.driver.Driver.driver; import static net.corda.testing.driver.Driver.driver;
import static net.corda.testing.node.internal.TestCordappsUtilsKt.FINANCE_CORDAPPS;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
public class JavaIntegrationTestingTutorial { public class JavaIntegrationTestingTutorial {
@ -40,7 +41,7 @@ public class JavaIntegrationTestingTutorial {
// START 1 // START 1
driver(new DriverParameters() driver(new DriverParameters()
.withStartNodesInProcess(true) .withStartNodesInProcess(true)
.withExtraCordappPackagesToScan(singletonList("net.corda.finance")), dsl -> { .withCordappsForAllNodes(FINANCE_CORDAPPS), dsl -> {
User aliceUser = new User("aliceUser", "testPassword1", new HashSet<>(asList( User aliceUser = new User("aliceUser", "testPassword1", new HashSet<>(asList(
startFlow(CashIssueAndPaymentFlow.class), startFlow(CashIssueAndPaymentFlow.class),

View File

@ -20,6 +20,7 @@ import net.corda.testing.core.*
import net.corda.testing.driver.DriverParameters import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
import net.corda.testing.node.User import net.corda.testing.node.User
import net.corda.testing.node.internal.FINANCE_CORDAPPS
import org.junit.Test import org.junit.Test
import rx.Observable import rx.Observable
import java.util.* import java.util.*
@ -29,7 +30,7 @@ class KotlinIntegrationTestingTutorial {
@Test @Test
fun `alice bob cash exchange example`() { fun `alice bob cash exchange example`() {
// START 1 // START 1
driver(DriverParameters(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.finance"))) { driver(DriverParameters(startNodesInProcess = true, cordappsForAllNodes = FINANCE_CORDAPPS)) {
val aliceUser = User("aliceUser", "testPassword1", permissions = setOf( val aliceUser = User("aliceUser", "testPassword1", permissions = setOf(
startFlow<CashIssueAndPaymentFlow>(), startFlow<CashIssueAndPaymentFlow>(),
invokeRpc("vaultTrackBy") invokeRpc("vaultTrackBy")

View File

@ -176,8 +176,8 @@ has been adjusted in the same way as ``FinalityFlow`` above, to close problems w
outside of other flow context. Old code will still work, but it is recommended to adjust your call sites so a session is passed into outside of other flow context. Old code will still work, but it is recommended to adjust your call sites so a session is passed into
the ``SwapIdentitiesFlow``. the ``SwapIdentitiesFlow``.
Step 5. Possibly, adjust unit test code Step 5. Possibly, adjust test code
--------------------------------------- ----------------------------------
``MockNodeParameters`` and functions creating it no longer use a lambda expecting a ``NodeConfiguration`` object. ``MockNodeParameters`` and functions creating it no longer use a lambda expecting a ``NodeConfiguration`` object.
Use a ``MockNetworkConfigOverrides`` object instead. This is an API change we regret, but unfortunately in Corda 3 we accidentally exposed Use a ``MockNetworkConfigOverrides`` object instead. This is an API change we regret, but unfortunately in Corda 3 we accidentally exposed
@ -202,6 +202,9 @@ becomes::
initialIdentity = TestIdentity(CordaX500Name("TestIdentity", "", "GB")) initialIdentity = TestIdentity(CordaX500Name("TestIdentity", "", "GB"))
) )
You may need to use the new ``TestCordapp`` API when testing with the node driver or mock network, especially if you decide to stick with the
pre-Corda 4 ``FinalityFlow`` API. The previous way of pulling in CorDapps into your tests does not honour CorDapp versioning.
Step 6. Security: refactor to avoid violating sealed packages Step 6. Security: refactor to avoid violating sealed packages
------------------------------------------------------------- -------------------------------------------------------------

View File

@ -7,7 +7,7 @@ import net.corda.finance.USD
import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNetworkParameters import net.corda.testing.node.MockNetworkParameters
import net.corda.testing.node.MockNodeParameters import net.corda.testing.node.MockNodeParameters
import net.corda.testing.node.internal.cordappForPackages import net.corda.testing.node.internal.cordappWithPackages
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.After import org.junit.After
import org.junit.Test import org.junit.Test
@ -21,7 +21,7 @@ class CashConfigDataFlowTest {
@Test @Test
fun `issuable currencies read in from cordapp config`() { fun `issuable currencies read in from cordapp config`() {
val node = mockNet.createNode(MockNodeParameters(additionalCordapps = listOf( val node = mockNet.createNode(MockNodeParameters(additionalCordapps = listOf(
cordappForPackages(javaClass.packageName).withConfig(mapOf("issuableCurrencies" to listOf("EUR", "USD"))) cordappWithPackages(javaClass.packageName).copy(config = mapOf("issuableCurrencies" to listOf("EUR", "USD")))
))) )))
val config = node.startFlow(CashConfigDataFlow()).getOrThrow() val config = node.startFlow(CashConfigDataFlow()).getOrThrow()
assertThat(config.issuableCurrencies).containsExactly(EUR, USD) assertThat(config.issuableCurrencies).containsExactly(EUR, USD)

View File

@ -26,8 +26,7 @@ import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.MockCordappConfigProvider import net.corda.testing.internal.MockCordappConfigProvider
import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.internal.TestCordappDirectories import net.corda.testing.node.internal.cordappWithPackages
import net.corda.testing.node.internal.cordappsForPackages
import net.corda.testing.services.MockAttachmentStorage import net.corda.testing.services.MockAttachmentStorage
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
@ -112,7 +111,6 @@ class AttachmentsClassLoaderStaticContractTests {
} }
private fun cordappLoaderForPackages(packages: Collection<String>): CordappLoader { private fun cordappLoaderForPackages(packages: Collection<String>): CordappLoader {
val dirs = cordappsForPackages(packages).map { TestCordappDirectories.getJarDirectory(it) } return JarScanningCordappLoader.fromJarUrls(listOf(cordappWithPackages(*packages.toTypedArray()).jarFile.toUri().toURL()))
return JarScanningCordappLoader.fromDirectories(dirs)
} }
} }

View File

@ -6,6 +6,8 @@ import net.corda.core.contracts.*
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StartableByRPC import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.deleteRecursively
import net.corda.core.internal.div
import net.corda.core.internal.packageName import net.corda.core.internal.packageName
import net.corda.core.messaging.startFlow import net.corda.core.messaging.startFlow
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
@ -23,7 +25,7 @@ import net.corda.testing.core.singleIdentity
import net.corda.testing.driver.NodeParameters import net.corda.testing.driver.NodeParameters
import net.corda.testing.driver.internal.incrementalPortAllocation import net.corda.testing.driver.internal.incrementalPortAllocation
import net.corda.testing.node.User import net.corda.testing.node.User
import net.corda.testing.node.internal.cordappForPackages import net.corda.testing.node.internal.cordappWithPackages
import net.corda.testing.node.internal.internalDriver import net.corda.testing.node.internal.internalDriver
import org.junit.Assume.assumeFalse import org.junit.Assume.assumeFalse
import org.junit.Test import org.junit.Test
@ -34,9 +36,9 @@ import kotlin.test.assertNotNull
class SignatureConstraintVersioningTests { class SignatureConstraintVersioningTests {
private val base = cordappForPackages(MessageState::class.packageName, DummyMessageContract::class.packageName) private val base = cordappWithPackages(MessageState::class.packageName, DummyMessageContract::class.packageName).signed()
private val oldCordapp = base.withVersion("2") private val oldCordapp = base.copy(versionId = 2)
private val newCordapp = base.withVersion("3") private val newCordapp = base.copy(versionId = 3)
private val user = User("mark", "dadada", setOf(startFlow<CreateMessage>(), startFlow<ConsumeMessage>(), invokeRpc("vaultQuery"))) private val user = User("mark", "dadada", setOf(startFlow<CreateMessage>(), startFlow<ConsumeMessage>(), invokeRpc("vaultQuery")))
private val message = Message("Hello world!") private val message = Message("Hello world!")
private val transformetMessage = Message(message.value + "A") private val transformetMessage = Message(message.value + "A")
@ -45,11 +47,13 @@ class SignatureConstraintVersioningTests {
fun `can evolve from lower contract class version to higher one`() { fun `can evolve from lower contract class version to higher one`() {
assumeFalse(System.getProperty("os.name").toLowerCase().startsWith("win")) // See NodeStatePersistenceTests.kt. assumeFalse(System.getProperty("os.name").toLowerCase().startsWith("win")) // See NodeStatePersistenceTests.kt.
val stateAndRef: StateAndRef<MessageState>? = internalDriver(inMemoryDB = false, val stateAndRef: StateAndRef<MessageState>? = internalDriver(
inMemoryDB = false,
startNodesInProcess = isQuasarAgentSpecified(), startNodesInProcess = isQuasarAgentSpecified(),
networkParameters = testNetworkParameters(notaries = emptyList(), minimumPlatformVersion = 4), signCordapps = true) { networkParameters = testNetworkParameters(notaries = emptyList(), minimumPlatformVersion = 4)
var nodeName = { ) {
val nodeHandle = startNode(NodeParameters(rpcUsers = listOf(user), additionalCordapps = listOf(oldCordapp), regenerateCordappsOnStart = true)).getOrThrow() val nodeName = {
val nodeHandle = startNode(NodeParameters(rpcUsers = listOf(user), additionalCordapps = listOf(oldCordapp))).getOrThrow()
val nodeName = nodeHandle.nodeInfo.singleIdentity().name val nodeName = nodeHandle.nodeInfo.singleIdentity().name
CordaRPCClient(nodeHandle.rpcAddress).start(user.username, user.password).use { CordaRPCClient(nodeHandle.rpcAddress).start(user.username, user.password).use {
it.proxy.startFlow(::CreateMessage, message, defaultNotaryIdentity).returnValue.getOrThrow() it.proxy.startFlow(::CreateMessage, message, defaultNotaryIdentity).returnValue.getOrThrow()
@ -57,8 +61,9 @@ class SignatureConstraintVersioningTests {
nodeHandle.stop() nodeHandle.stop()
nodeName nodeName
}() }()
var result = { val result = {
val nodeHandle = startNode(NodeParameters(providedName = nodeName, rpcUsers = listOf(user), additionalCordapps = listOf(newCordapp), regenerateCordappsOnStart = true)).getOrThrow() (baseDirectory(nodeName) / "cordapps").deleteRecursively()
val nodeHandle = startNode(NodeParameters(providedName = nodeName, rpcUsers = listOf(user), additionalCordapps = listOf(newCordapp))).getOrThrow()
var result: StateAndRef<MessageState>? = CordaRPCClient(nodeHandle.rpcAddress).start(user.username, user.password).use { var result: StateAndRef<MessageState>? = CordaRPCClient(nodeHandle.rpcAddress).start(user.username, user.password).use {
val page = it.proxy.vaultQuery(MessageState::class.java) val page = it.proxy.vaultQuery(MessageState::class.java)
page.states.singleOrNull() page.states.singleOrNull()
@ -87,9 +92,9 @@ class SignatureConstraintVersioningTests {
val stateAndRef: StateAndRef<MessageState>? = internalDriver(inMemoryDB = false, val stateAndRef: StateAndRef<MessageState>? = internalDriver(inMemoryDB = false,
startNodesInProcess = isQuasarAgentSpecified(), startNodesInProcess = isQuasarAgentSpecified(),
networkParameters = testNetworkParameters(notaries = emptyList(), minimumPlatformVersion = 4), signCordapps = true) { networkParameters = testNetworkParameters(notaries = emptyList(), minimumPlatformVersion = 4)) {
var nodeName = { val nodeName = {
val nodeHandle = startNode(NodeParameters(rpcUsers = listOf(user), additionalCordapps = listOf(newCordapp), regenerateCordappsOnStart = true), val nodeHandle = startNode(NodeParameters(rpcUsers = listOf(user), additionalCordapps = listOf(newCordapp)),
customOverrides = mapOf("h2Settings.address" to "localhost:$port")).getOrThrow() customOverrides = mapOf("h2Settings.address" to "localhost:$port")).getOrThrow()
val nodeName = nodeHandle.nodeInfo.singleIdentity().name val nodeName = nodeHandle.nodeInfo.singleIdentity().name
CordaRPCClient(nodeHandle.rpcAddress).start(user.username, user.password).use { CordaRPCClient(nodeHandle.rpcAddress).start(user.username, user.password).use {
@ -98,8 +103,9 @@ class SignatureConstraintVersioningTests {
nodeHandle.stop() nodeHandle.stop()
nodeName nodeName
}() }()
var result = { val result = {
val nodeHandle = startNode(NodeParameters(providedName = nodeName, rpcUsers = listOf(user), additionalCordapps = listOf(oldCordapp), regenerateCordappsOnStart = true), (baseDirectory(nodeName) / "cordapps").deleteRecursively()
val nodeHandle = startNode(NodeParameters(providedName = nodeName, rpcUsers = listOf(user), additionalCordapps = listOf(oldCordapp)),
customOverrides = mapOf("h2Settings.address" to "localhost:$port")).getOrThrow() customOverrides = mapOf("h2Settings.address" to "localhost:$port")).getOrThrow()
//set the attachment with newer version (3) as untrusted one so the node can use only the older attachment with version 2 //set the attachment with newer version (3) as untrusted one so the node can use only the older attachment with version 2

View File

@ -4,6 +4,8 @@ import net.corda.core.contracts.HashAttachmentConstraint
import net.corda.core.contracts.SignatureAttachmentConstraint import net.corda.core.contracts.SignatureAttachmentConstraint
import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.withoutIssuer import net.corda.core.contracts.withoutIssuer
import net.corda.core.internal.deleteRecursively
import net.corda.core.internal.div
import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.startFlow import net.corda.core.messaging.startFlow
import net.corda.core.messaging.vaultQueryBy import net.corda.core.messaging.vaultQueryBy
@ -24,8 +26,8 @@ import net.corda.testing.core.internal.SelfCleaningDir
import net.corda.testing.driver.* import net.corda.testing.driver.*
import net.corda.testing.node.NotarySpec import net.corda.testing.node.NotarySpec
import net.corda.testing.node.User import net.corda.testing.node.User
import net.corda.testing.node.internal.FINANCE_CORDAPP import net.corda.testing.node.internal.cordappWithPackages
import org.assertj.core.api.Assertions import org.assertj.core.api.Assertions.assertThat
import org.junit.Test import org.junit.Test
class CordappConstraintsTests { class CordappConstraintsTests {
@ -35,6 +37,8 @@ class CordappConstraintsTests {
invokeRpc(CordaRPCOps::wellKnownPartyFromX500Name), invokeRpc(CordaRPCOps::wellKnownPartyFromX500Name),
invokeRpc(CordaRPCOps::notaryIdentities), invokeRpc(CordaRPCOps::notaryIdentities),
invokeRpc("vaultTrackByCriteria"))) invokeRpc("vaultTrackByCriteria")))
val UNSIGNED_FINANCE_CORDAPP = cordappWithPackages("net.corda.finance")
val SIGNED_FINANCE_CORDAPP = UNSIGNED_FINANCE_CORDAPP.signed()
} }
@Test @Test
@ -43,10 +47,11 @@ class CordappConstraintsTests {
networkParameters = testNetworkParameters(minimumPlatformVersion = 4), networkParameters = testNetworkParameters(minimumPlatformVersion = 4),
inMemoryDB = false inMemoryDB = false
)) { )) {
val alice = startNode(NodeParameters(
val alice = startNode(NodeParameters(additionalCordapps = listOf(FINANCE_CORDAPP.signJar()), additionalCordapps = listOf(SIGNED_FINANCE_CORDAPP),
providedName = ALICE_NAME, providedName = ALICE_NAME,
rpcUsers = listOf(user))).getOrThrow() rpcUsers = listOf(user)
)).getOrThrow()
val expected = 500.DOLLARS val expected = 500.DOLLARS
val ref = OpaqueBytes.of(0x01) val ref = OpaqueBytes.of(0x01)
@ -57,8 +62,8 @@ class CordappConstraintsTests {
val states = alice.rpc.vaultQueryBy<Cash.State>().states val states = alice.rpc.vaultQueryBy<Cash.State>().states
printVault(alice, states) printVault(alice, states)
Assertions.assertThat(states).hasSize(1) assertThat(states).hasSize(1)
Assertions.assertThat(states[0].state.constraint is SignatureAttachmentConstraint) assertThat(states[0].state.constraint).isInstanceOf(SignatureAttachmentConstraint::class.java)
} }
} }
@ -68,11 +73,12 @@ class CordappConstraintsTests {
networkParameters = testNetworkParameters(minimumPlatformVersion = 4), networkParameters = testNetworkParameters(minimumPlatformVersion = 4),
inMemoryDB = false inMemoryDB = false
)) { )) {
println("Starting the node using unsigned contract jar ...") println("Starting the node using unsigned contract jar ...")
val alice = startNode(NodeParameters(providedName = ALICE_NAME, val alice = startNode(NodeParameters(
additionalCordapps = listOf(FINANCE_CORDAPP), providedName = ALICE_NAME,
rpcUsers = listOf(user))).getOrThrow() additionalCordapps = listOf(UNSIGNED_FINANCE_CORDAPP),
rpcUsers = listOf(user)
)).getOrThrow()
val expected = 500.DOLLARS val expected = 500.DOLLARS
val ref = OpaqueBytes.of(0x01) val ref = OpaqueBytes.of(0x01)
@ -82,7 +88,7 @@ class CordappConstraintsTests {
// Query vault // Query vault
val states = alice.rpc.vaultQueryBy<Cash.State>().states val states = alice.rpc.vaultQueryBy<Cash.State>().states
printVault(alice, states) printVault(alice, states)
Assertions.assertThat(states).hasSize(1) assertThat(states).hasSize(1)
// Restart the node and re-query the vault // Restart the node and re-query the vault
println("Shutting down the node ...") println("Shutting down the node ...")
@ -90,14 +96,15 @@ class CordappConstraintsTests {
alice.stop() alice.stop()
println("Restarting the node using signed contract jar ...") println("Restarting the node using signed contract jar ...")
val restartedNode = startNode(NodeParameters(providedName = ALICE_NAME, (baseDirectory(ALICE_NAME) / "cordapps").deleteRecursively()
additionalCordapps = listOf(FINANCE_CORDAPP.signJar()), val restartedNode = startNode(NodeParameters(
regenerateCordappsOnStart = true providedName = ALICE_NAME,
additionalCordapps = listOf(SIGNED_FINANCE_CORDAPP)
)).getOrThrow() )).getOrThrow()
val statesAfterRestart = restartedNode.rpc.vaultQueryBy<Cash.State>().states val statesAfterRestart = restartedNode.rpc.vaultQueryBy<Cash.State>().states
printVault(restartedNode, statesAfterRestart) printVault(restartedNode, statesAfterRestart)
Assertions.assertThat(statesAfterRestart).hasSize(1) assertThat(statesAfterRestart).hasSize(1)
val issueTx2 = restartedNode.rpc.startFlow(::CashIssueFlow, expected, ref, defaultNotaryIdentity).returnValue.getOrThrow() val issueTx2 = restartedNode.rpc.startFlow(::CashIssueFlow, expected, ref, defaultNotaryIdentity).returnValue.getOrThrow()
println("Issued 2nd transaction: $issueTx2") println("Issued 2nd transaction: $issueTx2")
@ -106,19 +113,19 @@ class CordappConstraintsTests {
val allStates = restartedNode.rpc.vaultQueryBy<Cash.State>().states val allStates = restartedNode.rpc.vaultQueryBy<Cash.State>().states
printVault(restartedNode, allStates) printVault(restartedNode, allStates)
Assertions.assertThat(allStates).hasSize(2) assertThat(allStates).hasSize(2)
Assertions.assertThat(allStates[0].state.constraint is HashAttachmentConstraint) assertThat(allStates[0].state.constraint).isInstanceOf(HashAttachmentConstraint::class.java)
Assertions.assertThat(allStates[1].state.constraint is SignatureAttachmentConstraint) assertThat(allStates[1].state.constraint).isInstanceOf(SignatureAttachmentConstraint::class.java)
} }
} }
@Test @Test
fun `issue and consume cash using hash constraints`() { fun `issue and consume cash using hash constraints`() {
driver(DriverParameters(cordappsForAllNodes = listOf(FINANCE_CORDAPP), driver(DriverParameters(
extraCordappPackagesToScan = listOf("net.corda.finance"), cordappsForAllNodes = listOf(UNSIGNED_FINANCE_CORDAPP),
networkParameters = testNetworkParameters(minimumPlatformVersion = 4), networkParameters = testNetworkParameters(minimumPlatformVersion = 4),
inMemoryDB = false)) { inMemoryDB = false
)) {
val (alice, bob) = listOf( val (alice, bob) = listOf(
startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)), startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)),
startNode(providedName = BOB_NAME, rpcUsers = listOf(user)) startNode(providedName = BOB_NAME, rpcUsers = listOf(user))
@ -145,10 +152,10 @@ class CordappConstraintsTests {
val aliceStates = aliceQuery.states val aliceStates = aliceQuery.states
printVault(alice, aliceQuery.states) printVault(alice, aliceQuery.states)
Assertions.assertThat(aliceStates).hasSize(1) assertThat(aliceStates).hasSize(1)
Assertions.assertThat(aliceStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS) assertThat(aliceStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS)
Assertions.assertThat(aliceQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.CONSUMED) assertThat(aliceQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.CONSUMED)
Assertions.assertThat(aliceQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.HASH) assertThat(aliceQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.HASH)
// Check Bob Vault Updates // Check Bob Vault Updates
vaultUpdatesBob.expectEvents { vaultUpdatesBob.expectEvents {
@ -165,23 +172,23 @@ class CordappConstraintsTests {
val bobStates = bobQuery.states val bobStates = bobQuery.states
printVault(bob, bobQuery.states) printVault(bob, bobQuery.states)
Assertions.assertThat(bobStates).hasSize(1) assertThat(bobStates).hasSize(1)
Assertions.assertThat(bobStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS) assertThat(bobStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS)
Assertions.assertThat(bobQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.UNCONSUMED) assertThat(bobQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.UNCONSUMED)
Assertions.assertThat(bobQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.HASH) assertThat(bobQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.HASH)
} }
} }
@Test @Test
fun `issue and consume cash using signature constraints`() { fun `issue and consume cash using signature constraints`() {
driver(DriverParameters(cordappsForAllNodes = listOf(FINANCE_CORDAPP.signJar()), driver(DriverParameters(
extraCordappPackagesToScan = listOf("net.corda.finance"), cordappsForAllNodes = listOf(SIGNED_FINANCE_CORDAPP),
networkParameters = testNetworkParameters(minimumPlatformVersion = 4), networkParameters = testNetworkParameters(minimumPlatformVersion = 4),
inMemoryDB = false)) { inMemoryDB = false
)) {
val (alice, bob) = listOf( val (alice, bob) = listOf(
startNode(NodeParameters(providedName = ALICE_NAME, rpcUsers = listOf(user), additionalCordapps = listOf(FINANCE_CORDAPP.signJar()))), startNode(NodeParameters(providedName = ALICE_NAME, rpcUsers = listOf(user))),
startNode(NodeParameters(providedName = BOB_NAME, rpcUsers = listOf(user), additionalCordapps = listOf(FINANCE_CORDAPP.signJar()))) startNode(NodeParameters(providedName = BOB_NAME, rpcUsers = listOf(user)))
).map { it.getOrThrow() } ).map { it.getOrThrow() }
// Issue Cash // Issue Cash
@ -205,10 +212,10 @@ class CordappConstraintsTests {
val aliceStates = aliceQuery.states val aliceStates = aliceQuery.states
printVault(alice, aliceStates) printVault(alice, aliceStates)
Assertions.assertThat(aliceStates).hasSize(1) assertThat(aliceStates).hasSize(1)
Assertions.assertThat(aliceStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS) assertThat(aliceStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS)
Assertions.assertThat(aliceQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.CONSUMED) assertThat(aliceQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.CONSUMED)
Assertions.assertThat(aliceQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.SIGNATURE) assertThat(aliceQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.SIGNATURE)
// Check Bob Vault Updates // Check Bob Vault Updates
vaultUpdatesBob.expectEvents { vaultUpdatesBob.expectEvents {
@ -225,27 +232,28 @@ class CordappConstraintsTests {
val bobStates = bobQuery.states val bobStates = bobQuery.states
printVault(bob, bobStates) printVault(bob, bobStates)
Assertions.assertThat(bobStates).hasSize(1) assertThat(bobStates).hasSize(1)
Assertions.assertThat(bobStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS) assertThat(bobStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS)
Assertions.assertThat(bobQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.UNCONSUMED) assertThat(bobQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.UNCONSUMED)
Assertions.assertThat(bobQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.SIGNATURE) assertThat(bobQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.SIGNATURE)
} }
} }
@Test @Test
fun `issue cash and transfer using hash to signature constraints migration`() { fun `issue cash and transfer using hash to signature constraints migration`() {
// signing key setup // signing key setup
val keyStoreDir = SelfCleaningDir() val keyStoreDir = SelfCleaningDir()
val packageOwnerKey = keyStoreDir.path.generateKey() val packageOwnerKey = keyStoreDir.path.generateKey()
driver(DriverParameters(cordappsForAllNodes = listOf(FINANCE_CORDAPP), driver(DriverParameters(
cordappsForAllNodes = listOf(UNSIGNED_FINANCE_CORDAPP),
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = false)), notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = false)),
extraCordappPackagesToScan = listOf("net.corda.finance"), networkParameters = testNetworkParameters(
networkParameters = testNetworkParameters(minimumPlatformVersion = 4, minimumPlatformVersion = 4,
packageOwnership = mapOf("net.corda.finance.contracts.asset" to packageOwnerKey)), packageOwnership = mapOf("net.corda.finance.contracts.asset" to packageOwnerKey)
inMemoryDB = false)) { ),
inMemoryDB = false
)) {
val (alice, bob) = listOf( val (alice, bob) = listOf(
startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)), startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)),
startNode(providedName = BOB_NAME, rpcUsers = listOf(user)) startNode(providedName = BOB_NAME, rpcUsers = listOf(user))
@ -270,15 +278,17 @@ class CordappConstraintsTests {
bob.stop() bob.stop()
println("Restarting the node for $ALICE_NAME ...") println("Restarting the node for $ALICE_NAME ...")
val restartedAlice = startNode(NodeParameters(providedName = ALICE_NAME, (baseDirectory(ALICE_NAME) / "cordapps").deleteRecursively()
additionalCordapps = listOf(FINANCE_CORDAPP.signJar(keyStoreDir.path)), val restartedAlice = startNode(NodeParameters(
regenerateCordappsOnStart = true providedName = ALICE_NAME,
additionalCordapps = listOf(UNSIGNED_FINANCE_CORDAPP.signed(keyStoreDir.path))
)).getOrThrow() )).getOrThrow()
println("Restarting the node for $BOB_NAME ...") println("Restarting the node for $BOB_NAME ...")
val restartedBob = startNode(NodeParameters(providedName = BOB_NAME, (baseDirectory(BOB_NAME) / "cordapps").deleteRecursively()
additionalCordapps = listOf(FINANCE_CORDAPP.signJar(keyStoreDir.path)), val restartedBob = startNode(NodeParameters(
regenerateCordappsOnStart = true providedName = BOB_NAME,
additionalCordapps = listOf(UNSIGNED_FINANCE_CORDAPP.signed(keyStoreDir.path))
)).getOrThrow() )).getOrThrow()
// Register for Bob vault updates // Register for Bob vault updates
@ -295,10 +305,10 @@ class CordappConstraintsTests {
val aliceStates = aliceQuery.states val aliceStates = aliceQuery.states
printVault(alice, aliceStates) printVault(alice, aliceStates)
Assertions.assertThat(aliceStates).hasSize(1) assertThat(aliceStates).hasSize(1)
Assertions.assertThat(aliceStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS) assertThat(aliceStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS)
Assertions.assertThat(aliceQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.CONSUMED) assertThat(aliceQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.CONSUMED)
Assertions.assertThat(aliceQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.HASH) assertThat(aliceQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.HASH)
// Check Bob Vault Updates // Check Bob Vault Updates
vaultUpdatesBob.expectEvents { vaultUpdatesBob.expectEvents {
@ -316,10 +326,10 @@ class CordappConstraintsTests {
val bobStates = bobQuery.states val bobStates = bobQuery.states
printVault(bob, bobStates) printVault(bob, bobStates)
Assertions.assertThat(bobStates).hasSize(1) assertThat(bobStates).hasSize(1)
Assertions.assertThat(bobStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS) assertThat(bobStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS)
Assertions.assertThat(bobQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.UNCONSUMED) assertThat(bobQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.UNCONSUMED)
Assertions.assertThat(bobQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.SIGNATURE) assertThat(bobQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.SIGNATURE)
// clean-up // clean-up
keyStoreDir.close() keyStoreDir.close()

View File

@ -22,7 +22,7 @@ import net.corda.testing.driver.internal.internalServices
import net.corda.testing.internal.performance.div import net.corda.testing.internal.performance.div
import net.corda.testing.node.NotarySpec import net.corda.testing.node.NotarySpec
import net.corda.testing.node.User import net.corda.testing.node.User
import net.corda.testing.node.internal.DriverDSLImpl.Companion.cordappsInCurrentAndAdditionalPackages import net.corda.testing.node.internal.FINANCE_CORDAPPS
import net.corda.testing.node.internal.internalDriver import net.corda.testing.node.internal.internalDriver
import net.corda.testing.node.internal.performance.startPublishingFixedRateInjector import net.corda.testing.node.internal.performance.startPublishingFixedRateInjector
import net.corda.testing.node.internal.performance.startReporter import net.corda.testing.node.internal.performance.startReporter
@ -97,7 +97,7 @@ class NodePerformanceTests {
internalDriver( internalDriver(
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, rpcUsers = listOf(user))), notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, rpcUsers = listOf(user))),
startNodesInProcess = true, startNodesInProcess = true,
cordappsForAllNodes = cordappsInCurrentAndAdditionalPackages("net.corda.finance") cordappsForAllNodes = FINANCE_CORDAPPS
) { ) {
val notary = defaultNotaryNode.getOrThrow() as InProcess val notary = defaultNotaryNode.getOrThrow() as InProcess
val metricRegistry = startReporter(this.shutdownManager, notary.internalServices.monitoringService.metrics) val metricRegistry = startReporter(this.shutdownManager, notary.internalServices.monitoringService.metrics)

View File

@ -18,12 +18,14 @@ import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.NodeParameters import net.corda.testing.driver.NodeParameters
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
import net.corda.testing.node.TestCordapp import net.corda.testing.node.TestCordapp
import net.corda.testing.node.internal.CustomCordapp
import net.corda.testing.node.internal.ListenProcessDeathException import net.corda.testing.node.internal.ListenProcessDeathException
import net.corda.testing.node.internal.TestCordappDirectories
import net.corda.testing.node.internal.cordappForClasses import net.corda.testing.node.internal.cordappForClasses
import net.test.cordapp.v1.Record import net.test.cordapp.v1.Record
import net.test.cordapp.v1.SendMessageFlow import net.test.cordapp.v1.SendMessageFlow
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test import org.junit.Test
import java.nio.file.Path
import java.nio.file.StandardCopyOption.REPLACE_EXISTING import java.nio.file.StandardCopyOption.REPLACE_EXISTING
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -46,8 +48,8 @@ class FlowCheckpointVersionNodeStartupCheckTest {
@Test @Test
fun `restart node successfully with suspended flow`() { fun `restart node successfully with suspended flow`() {
return driver(parametersForRestartingNodes(listOf(defaultCordapp))) { return driver(parametersForRestartingNodes()) {
createSuspendedFlowInBob(cordapps = emptySet()) createSuspendedFlowInBob(setOf(defaultCordapp))
// Bob will resume the flow // Bob will resume the flow
val alice = startNode(providedName = ALICE_NAME).getOrThrow() val alice = startNode(providedName = ALICE_NAME).getOrThrow()
startNode(providedName = BOB_NAME).getOrThrow() startNode(providedName = BOB_NAME).getOrThrow()
@ -66,19 +68,20 @@ class FlowCheckpointVersionNodeStartupCheckTest {
@Test @Test
fun `restart node with incompatible version of suspended flow due to different jar name`() { fun `restart node with incompatible version of suspended flow due to different jar name`() {
driver(parametersForRestartingNodes()) { driver(parametersForRestartingNodes()) {
val cordapp = defaultCordapp.withName("different-jar-name-test-${UUID.randomUUID()}") val uniqueName = "different-jar-name-test-${UUID.randomUUID()}"
// Create the CorDapp jar file manually first to get hold of the directory that will contain it so that we can val cordapp = defaultCordapp.copy(name = uniqueName)
// rename the filename later. The cordappDir, which acts as pointer to the jar file, does not get renamed.
val cordappDir = TestCordappDirectories.getJarDirectory(cordapp)
val cordappJar = cordappDir.list().single { it.toString().endsWith(".jar") }
createSuspendedFlowInBob(setOf(cordapp)) val bobBaseDir = createSuspendedFlowInBob(setOf(cordapp))
val cordappsDir = bobBaseDir / "cordapps"
val cordappJar = cordappsDir.list().single { it.toString().endsWith(".jar") }
// Make sure we're dealing with right jar
assertThat(cordappJar.fileName.toString()).contains(uniqueName)
// Rename the jar file. // Rename the jar file.
cordappJar.moveTo(cordappDir / "renamed-${cordappJar.fileName}") cordappJar.moveTo(cordappsDir / "renamed-${cordappJar.fileName}")
assertBobFailsToStartWithLogMessage( assertBobFailsToStartWithLogMessage(
setOf(cordapp), emptyList(),
CheckpointIncompatibleException.FlowNotInstalledException(SendMessageFlow::class.java).message CheckpointIncompatibleException.FlowNotInstalledException(SendMessageFlow::class.java).message
) )
} }
@ -87,25 +90,30 @@ class FlowCheckpointVersionNodeStartupCheckTest {
@Test @Test
fun `restart node with incompatible version of suspended flow due to different jar hash`() { fun `restart node with incompatible version of suspended flow due to different jar hash`() {
driver(parametersForRestartingNodes()) { driver(parametersForRestartingNodes()) {
val originalCordapp = defaultCordapp.withName("different-jar-hash-test-${UUID.randomUUID()}") val uniqueName = "different-jar-hash-test-${UUID.randomUUID()}"
val originalCordappJar = TestCordappDirectories.getJarDirectory(originalCordapp).list().single { it.toString().endsWith(".jar") } val cordapp = defaultCordapp.copy(name = uniqueName)
createSuspendedFlowInBob(setOf(originalCordapp)) val bobBaseDir = createSuspendedFlowInBob(setOf(cordapp))
// The vendor is part of the MANIFEST so changing it is sufficient to change the jar hash val cordappsDir = bobBaseDir / "cordapps"
val modifiedCordapp = originalCordapp.withVendor("${originalCordapp.vendor}-modified") val cordappJar = cordappsDir.list().single { it.toString().endsWith(".jar") }
val modifiedCordappJar = TestCordappDirectories.getJarDirectory(modifiedCordapp).list().single { it.toString().endsWith(".jar") } // Make sure we're dealing with right jar
modifiedCordappJar.moveTo(originalCordappJar, REPLACE_EXISTING) assertThat(cordappJar.fileName.toString()).contains(uniqueName)
// The name is part of the MANIFEST so changing it is sufficient to change the jar hash
val modifiedCordapp = cordapp.copy(name = "${cordapp.name}-modified")
val modifiedCordappJar = CustomCordapp.getJarFile(modifiedCordapp)
modifiedCordappJar.moveTo(cordappJar, REPLACE_EXISTING)
assertBobFailsToStartWithLogMessage( assertBobFailsToStartWithLogMessage(
setOf(originalCordapp), emptyList(),
// The part of the log message generated by CheckpointIncompatibleException.FlowVersionIncompatibleException // The part of the log message generated by CheckpointIncompatibleException.FlowVersionIncompatibleException
"that is incompatible with the current installed version of" "that is incompatible with the current installed version of"
) )
} }
} }
private fun DriverDSL.createSuspendedFlowInBob(cordapps: Set<TestCordapp>) { private fun DriverDSL.createSuspendedFlowInBob(cordapps: Set<TestCordapp>): Path {
val (alice, bob) = listOf(ALICE_NAME, BOB_NAME) val (alice, bob) = listOf(ALICE_NAME, BOB_NAME)
.map { startNode(NodeParameters(providedName = it, additionalCordapps = cordapps)) } .map { startNode(NodeParameters(providedName = it, additionalCordapps = cordapps)) }
.transpose() .transpose()
@ -115,6 +123,7 @@ class FlowCheckpointVersionNodeStartupCheckTest {
// Wait until Bob progresses as far as possible because Alice node is offline // Wait until Bob progresses as far as possible because Alice node is offline
flowTracker.takeFirst { it == SendMessageFlow.Companion.FINALISING_TRANSACTION.label }.toBlocking().single() flowTracker.takeFirst { it == SendMessageFlow.Companion.FINALISING_TRANSACTION.label }.toBlocking().single()
bob.stop() bob.stop()
return bob.baseDirectory
} }
private fun DriverDSL.assertBobFailsToStartWithLogMessage(cordapps: Collection<TestCordapp>, logMessage: String) { private fun DriverDSL.assertBobFailsToStartWithLogMessage(cordapps: Collection<TestCordapp>, logMessage: String) {
@ -122,8 +131,7 @@ class FlowCheckpointVersionNodeStartupCheckTest {
startNode(NodeParameters( startNode(NodeParameters(
providedName = BOB_NAME, providedName = BOB_NAME,
customOverrides = mapOf("devMode" to false), customOverrides = mapOf("devMode" to false),
additionalCordapps = cordapps, additionalCordapps = cordapps
regenerateCordappsOnStart = true
)).getOrThrow() )).getOrThrow()
} }
@ -133,11 +141,11 @@ class FlowCheckpointVersionNodeStartupCheckTest {
assertEquals(1, matchingLineCount) assertEquals(1, matchingLineCount)
} }
private fun parametersForRestartingNodes(cordappsForAllNodes: List<TestCordapp> = emptyList()): DriverParameters { private fun parametersForRestartingNodes(): DriverParameters {
return DriverParameters( return DriverParameters(
startNodesInProcess = false, // Start nodes in separate processes to ensure CordappLoader is not shared between restarts startNodesInProcess = false, // Start nodes in separate processes to ensure CordappLoader is not shared between restarts
inMemoryDB = false, // Ensure database is persisted between node restarts so we can keep suspended flows inMemoryDB = false, // Ensure database is persisted between node restarts so we can keep suspended flows
cordappsForAllNodes = cordappsForAllNodes cordappsForAllNodes = emptyList()
) )
} }
} }

View File

@ -20,7 +20,7 @@ import net.corda.testing.core.singleIdentity
import net.corda.testing.driver.internal.incrementalPortAllocation import net.corda.testing.driver.internal.incrementalPortAllocation
import net.corda.testing.internal.DEV_ROOT_CA import net.corda.testing.internal.DEV_ROOT_CA
import net.corda.testing.node.NotarySpec import net.corda.testing.node.NotarySpec
import net.corda.testing.node.internal.DriverDSLImpl.Companion.cordappsInCurrentAndAdditionalPackages import net.corda.testing.node.internal.FINANCE_CORDAPPS
import net.corda.testing.node.internal.SharedCompatibilityZoneParams import net.corda.testing.node.internal.SharedCompatibilityZoneParams
import net.corda.testing.node.internal.internalDriver import net.corda.testing.node.internal.internalDriver
import net.corda.testing.node.internal.network.NetworkMapServer import net.corda.testing.node.internal.network.NetworkMapServer
@ -88,7 +88,7 @@ class NodeRegistrationTest {
portAllocation = portAllocation, portAllocation = portAllocation,
compatibilityZone = compatibilityZone, compatibilityZone = compatibilityZone,
notarySpecs = listOf(NotarySpec(notaryName)), notarySpecs = listOf(NotarySpec(notaryName)),
cordappsForAllNodes = cordappsInCurrentAndAdditionalPackages("net.corda.finance"), cordappsForAllNodes = FINANCE_CORDAPPS,
notaryCustomOverrides = mapOf("devMode" to false) notaryCustomOverrides = mapOf("devMode" to false)
) { ) {
val (alice, genevieve) = listOf( val (alice, genevieve) = listOf(

View File

@ -5,8 +5,7 @@ import net.corda.core.flows.*
import net.corda.core.internal.packageName import net.corda.core.internal.packageName
import net.corda.node.VersionInfo import net.corda.node.VersionInfo
import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES
import net.corda.testing.node.internal.TestCordappDirectories import net.corda.testing.node.internal.cordappWithPackages
import net.corda.testing.node.internal.cordappForPackages
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Test import org.junit.Test
import java.nio.file.Paths import java.nio.file.Paths
@ -68,8 +67,8 @@ class JarScanningCordappLoaderTest {
@Test @Test
fun `flows are loaded by loader`() { fun `flows are loaded by loader`() {
val dir = TestCordappDirectories.getJarDirectory(cordappForPackages(javaClass.packageName)) val jarFile = cordappWithPackages(javaClass.packageName).jarFile
val loader = JarScanningCordappLoader.fromDirectories(listOf(dir)) val loader = JarScanningCordappLoader.fromJarUrls(listOf(jarFile.toUri().toURL()))
// One cordapp from this source tree. In gradle it will also pick up the node jar. // One cordapp from this source tree. In gradle it will also pick up the node jar.
assertThat(loader.cordapps).isNotEmpty assertThat(loader.cordapps).isNotEmpty

View File

@ -37,16 +37,13 @@ class FinalityHandlerTest {
fun `sent to flow hospital on error and attempted retry on node restart`() { fun `sent to flow hospital on error and attempted retry on node restart`() {
// Setup a network where only Alice has the finance CorDapp and it sends a cash tx to Bob who doesn't have the // Setup a network where only Alice has the finance CorDapp and it sends a cash tx to Bob who doesn't have the
// CorDapp. Bob's FinalityHandler will error when validating the tx. // CorDapp. Bob's FinalityHandler will error when validating the tx.
val alice = mockNet.createNode(InternalMockNodeParameters( val alice = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME, additionalCordapps = FINANCE_CORDAPPS))
legalName = ALICE_NAME,
additionalCordapps = setOf(FINANCE_CORDAPP)
))
var bob = mockNet.createNode(InternalMockNodeParameters( var bob = mockNet.createNode(InternalMockNodeParameters(
legalName = BOB_NAME, legalName = BOB_NAME,
// The node disables the FinalityHandler completely if there are no old CorDapps loaded, so we need to add // The node disables the FinalityHandler completely if there are no old CorDapps loaded, so we need to add
// a token old CorDapp to keep the handler running. // a token old CorDapp to keep the handler running.
additionalCordapps = setOf(cordappForPackages(javaClass.packageName).withTargetVersion(3)) additionalCordapps = setOf(cordappWithPackages(javaClass.packageName).copy(targetPlatformVersion = 3))
)) ))
val stx = alice.issueCashTo(bob) val stx = alice.issueCashTo(bob)
@ -67,15 +64,12 @@ class FinalityHandlerTest {
@Test @Test
fun `disabled if there are no old CorDapps loaded`() { fun `disabled if there are no old CorDapps loaded`() {
val alice = mockNet.createNode(InternalMockNodeParameters( val alice = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME, additionalCordapps = FINANCE_CORDAPPS))
legalName = ALICE_NAME,
additionalCordapps = setOf(FINANCE_CORDAPP)
))
val bob = mockNet.createNode(InternalMockNodeParameters( val bob = mockNet.createNode(InternalMockNodeParameters(
legalName = BOB_NAME, legalName = BOB_NAME,
// Make sure the target version is 4, and not the current platform version which may be greater // Make sure the target version is 4, and not the current platform version which may be greater
additionalCordapps = setOf(FINANCE_CORDAPP.withTargetVersion(4)) additionalCordapps = setOf(cordappWithPackages("net.corda.finance").copy(targetPlatformVersion = 4))
)) ))
val stx = alice.issueCashTo(bob) val stx = alice.issueCashTo(bob)

View File

@ -66,7 +66,7 @@ class FlowFrameworkTests {
@Before @Before
fun setUpMockNet() { fun setUpMockNet() {
mockNet = InternalMockNetwork( mockNet = InternalMockNetwork(
cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts") + FINANCE_CORDAPP, cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts") + FINANCE_CORDAPPS,
servicePeerAllocationStrategy = RoundRobin() servicePeerAllocationStrategy = RoundRobin()
) )

View File

@ -3,7 +3,7 @@ package net.corda.serialization.internal.amqp.custom
import net.corda.serialization.internal.amqp.SerializerFactory import net.corda.serialization.internal.amqp.SerializerFactory
import org.hamcrest.CoreMatchers.`is` import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.CoreMatchers.nullValue import org.hamcrest.CoreMatchers.nullValue
import org.junit.Assert import org.junit.Assert.assertThat
import org.junit.Test import org.junit.Test
import org.mockito.Mockito import org.mockito.Mockito
import java.util.* import java.util.*
@ -13,27 +13,27 @@ class OptionalSerializerTest {
fun `should convert optional with item to proxy`() { fun `should convert optional with item to proxy`() {
val opt = Optional.of("GenericTestString") val opt = Optional.of("GenericTestString")
val proxy = OptionalSerializer(Mockito.mock(SerializerFactory::class.java)).toProxy(opt) val proxy = OptionalSerializer(Mockito.mock(SerializerFactory::class.java)).toProxy(opt)
Assert.assertThat(proxy.item, `is`<Any>("GenericTestString")) assertThat(proxy.item, `is`<Any>("GenericTestString"))
} }
@Test @Test
fun `should convert optional without item to empty proxy`() { fun `should convert optional without item to empty proxy`() {
val opt = Optional.ofNullable<String>(null) val opt = Optional.ofNullable<String>(null)
val proxy = OptionalSerializer(Mockito.mock(SerializerFactory::class.java)).toProxy(opt) val proxy = OptionalSerializer(Mockito.mock(SerializerFactory::class.java)).toProxy(opt)
Assert.assertThat(proxy.item, `is`(nullValue())) assertThat(proxy.item, `is`(nullValue()))
} }
@Test @Test
fun `should convert proxy without item to empty optional `() { fun `should convert proxy without item to empty optional `() {
val proxy = OptionalSerializer.OptionalProxy(null) val proxy = OptionalSerializer.OptionalProxy(null)
val opt = OptionalSerializer(Mockito.mock(SerializerFactory::class.java)).fromProxy(proxy) val opt = OptionalSerializer(Mockito.mock(SerializerFactory::class.java)).fromProxy(proxy)
Assert.assertThat(opt.isPresent, `is`(false)) assertThat(opt.isPresent, `is`(false))
} }
@Test @Test
fun `should convert proxy with item to empty optional `() { fun `should convert proxy with item to empty optional `() {
val proxy = OptionalSerializer.OptionalProxy("GenericTestString") val proxy = OptionalSerializer.OptionalProxy("GenericTestString")
val opt = OptionalSerializer(Mockito.mock(SerializerFactory::class.java)).fromProxy(proxy) val opt = OptionalSerializer(Mockito.mock(SerializerFactory::class.java)).fromProxy(proxy)
Assert.assertThat(opt.get(), `is`<Any>("GenericTestString")) assertThat(opt.get(), `is`<Any>("GenericTestString"))
} }
} }

View File

@ -6,6 +6,8 @@ import net.corda.core.DoNotImplement
import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.CordaFuture
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.div
import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.CordaRPCOps
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
@ -19,7 +21,10 @@ import net.corda.testing.driver.internal.internalServices
import net.corda.testing.node.NotarySpec import net.corda.testing.node.NotarySpec
import net.corda.testing.node.TestCordapp import net.corda.testing.node.TestCordapp
import net.corda.testing.node.User import net.corda.testing.node.User
import net.corda.testing.node.internal.* import net.corda.testing.node.internal.DriverDSLImpl
import net.corda.testing.node.internal.genericDriver
import net.corda.testing.node.internal.getTimestampAsDirectoryName
import net.corda.testing.node.internal.newContext
import rx.Observable import rx.Observable
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
@ -136,7 +141,8 @@ constructor(
val startJmxHttpServer: Boolean = false, val startJmxHttpServer: Boolean = false,
val jmxHttpServerPortAllocation: PortAllocation = incrementalPortAllocation(7005) val jmxHttpServerPortAllocation: PortAllocation = incrementalPortAllocation(7005)
) { ) {
@Deprecated("The default constructor does not turn on monitoring. Simply leave the jmxPolicy parameter unspecified if you wish to not have monitoring turned on.") @Deprecated("The default constructor does not turn on monitoring. Simply leave the jmxPolicy parameter unspecified if you wish to not " +
"have monitoring turned on.")
constructor() : this(false) constructor() : this(false)
/** Create a [JmxPolicy] that turns on monitoring using the given [PortAllocation]. */ /** Create a [JmxPolicy] that turns on monitoring using the given [PortAllocation]. */
@ -178,14 +184,14 @@ fun <A> driver(defaultParameters: DriverParameters = DriverParameters(), dsl: Dr
isDebug = defaultParameters.isDebug, isDebug = defaultParameters.isDebug,
startNodesInProcess = defaultParameters.startNodesInProcess, startNodesInProcess = defaultParameters.startNodesInProcess,
waitForAllNodesToFinish = defaultParameters.waitForAllNodesToFinish, waitForAllNodesToFinish = defaultParameters.waitForAllNodesToFinish,
extraCordappPackagesToScan = defaultParameters.extraCordappPackagesToScan,
notarySpecs = defaultParameters.notarySpecs, notarySpecs = defaultParameters.notarySpecs,
jmxPolicy = defaultParameters.jmxPolicy, jmxPolicy = defaultParameters.jmxPolicy,
compatibilityZone = null, compatibilityZone = null,
networkParameters = defaultParameters.networkParameters, networkParameters = defaultParameters.networkParameters,
notaryCustomOverrides = defaultParameters.notaryCustomOverrides, notaryCustomOverrides = defaultParameters.notaryCustomOverrides,
inMemoryDB = defaultParameters.inMemoryDB, inMemoryDB = defaultParameters.inMemoryDB,
cordappsForAllNodes = defaultParameters.cordappsForAllNodes(), cordappsForAllNodes = uncheckedCast(defaultParameters.cordappsForAllNodes)
signCordapps = false
), ),
coerce = { it }, coerce = { it },
dsl = dsl dsl = dsl
@ -212,7 +218,8 @@ fun <A> driver(defaultParameters: DriverParameters = DriverParameters(), dsl: Dr
* available from [DriverDSL.notaryHandles], and will be added automatically to the network parameters. * available from [DriverDSL.notaryHandles], and will be added automatically to the network parameters.
* Defaults to a simple validating notary. * Defaults to a simple validating notary.
* @property extraCordappPackagesToScan A [List] of additional cordapp packages to scan for any cordapp code, e.g. * @property extraCordappPackagesToScan A [List] of additional cordapp packages to scan for any cordapp code, e.g.
* contract verification code, flows and services. The calling package is automatically added. * contract verification code, flows and services. The calling package is automatically included in this list. If this is not desirable
* then use [cordappsForAllNodes] instead.
* @property jmxPolicy Used to specify whether to expose JMX metrics via Jolokia HHTP/JSON. * @property jmxPolicy Used to specify whether to expose JMX metrics via Jolokia HHTP/JSON.
* @property networkParameters The network parameters to be used by all the nodes. [NetworkParameters.notaries] must be * @property networkParameters The network parameters to be used by all the nodes. [NetworkParameters.notaries] must be
* empty as notaries are defined by [notarySpecs]. * empty as notaries are defined by [notarySpecs].
@ -225,7 +232,7 @@ fun <A> driver(defaultParameters: DriverParameters = DriverParameters(), dsl: Dr
@Suppress("unused") @Suppress("unused")
data class DriverParameters( data class DriverParameters(
val isDebug: Boolean = false, val isDebug: Boolean = false,
val driverDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()), val driverDirectory: Path = Paths.get("build") / "node-driver" / getTimestampAsDirectoryName(),
val portAllocation: PortAllocation = incrementalPortAllocation(10000), val portAllocation: PortAllocation = incrementalPortAllocation(10000),
val debugPortAllocation: PortAllocation = incrementalPortAllocation(5005), val debugPortAllocation: PortAllocation = incrementalPortAllocation(5005),
val systemProperties: Map<String, String> = emptyMap(), val systemProperties: Map<String, String> = emptyMap(),
@ -233,6 +240,8 @@ data class DriverParameters(
val startNodesInProcess: Boolean = false, val startNodesInProcess: Boolean = false,
val waitForAllNodesToFinish: Boolean = false, val waitForAllNodesToFinish: Boolean = false,
val notarySpecs: List<NotarySpec> = listOf(NotarySpec(DUMMY_NOTARY_NAME)), val notarySpecs: List<NotarySpec> = listOf(NotarySpec(DUMMY_NOTARY_NAME)),
@Deprecated("extraCordappPackagesToScan does not preserve the original CorDapp's versioning and metadata, which may lead to " +
"misleading results in tests. Use cordappsForAllNodes instead.")
val extraCordappPackagesToScan: List<String> = emptyList(), val extraCordappPackagesToScan: List<String> = emptyList(),
@Suppress("DEPRECATION") val jmxPolicy: JmxPolicy = JmxPolicy(), @Suppress("DEPRECATION") val jmxPolicy: JmxPolicy = JmxPolicy(),
val networkParameters: NetworkParameters = testNetworkParameters(notaries = emptyList()), val networkParameters: NetworkParameters = testNetworkParameters(notaries = emptyList()),
@ -242,7 +251,7 @@ data class DriverParameters(
) { ) {
constructor( constructor(
isDebug: Boolean = false, isDebug: Boolean = false,
driverDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()), driverDirectory: Path = Paths.get("build") / "node-driver" / getTimestampAsDirectoryName(),
portAllocation: PortAllocation = incrementalPortAllocation(10000), portAllocation: PortAllocation = incrementalPortAllocation(10000),
debugPortAllocation: PortAllocation = incrementalPortAllocation(5005), debugPortAllocation: PortAllocation = incrementalPortAllocation(5005),
systemProperties: Map<String, String> = emptyMap(), systemProperties: Map<String, String> = emptyMap(),

View File

@ -20,8 +20,6 @@ import net.corda.testing.node.User
* @property maximumHeapSize The maximum JVM heap size to use for the node. Defaults to 512 MB. * @property maximumHeapSize The maximum JVM heap size to use for the node. Defaults to 512 MB.
* @property additionalCordapps Additional [TestCordapp]s that this node will have available, in addition to the ones common to all nodes * @property additionalCordapps Additional [TestCordapp]s that this node will have available, in addition to the ones common to all nodes
* managed by the [DriverDSL]. * managed by the [DriverDSL].
* @property regenerateCordappsOnStart Whether existing [TestCordapp]s unique to this node will be re-generated on start. Useful when stopping
* and restarting the same node.
*/ */
@Suppress("unused") @Suppress("unused")
data class NodeParameters( data class NodeParameters(
@ -32,7 +30,6 @@ data class NodeParameters(
val startInSameProcess: Boolean? = null, val startInSameProcess: Boolean? = null,
val maximumHeapSize: String = "512m", val maximumHeapSize: String = "512m",
val additionalCordapps: Collection<TestCordapp> = emptySet(), val additionalCordapps: Collection<TestCordapp> = emptySet(),
val regenerateCordappsOnStart: Boolean = false,
val flowOverrides: Map<out Class<out FlowLogic<*>>, Class<out FlowLogic<*>>> = emptyMap() val flowOverrides: Map<out Class<out FlowLogic<*>>, Class<out FlowLogic<*>>> = emptyMap()
) { ) {
/** /**
@ -48,7 +45,6 @@ data class NodeParameters(
fun withStartInSameProcess(startInSameProcess: Boolean?): NodeParameters = copy(startInSameProcess = startInSameProcess) fun withStartInSameProcess(startInSameProcess: Boolean?): NodeParameters = copy(startInSameProcess = startInSameProcess)
fun withMaximumHeapSize(maximumHeapSize: String): NodeParameters = copy(maximumHeapSize = maximumHeapSize) fun withMaximumHeapSize(maximumHeapSize: String): NodeParameters = copy(maximumHeapSize = maximumHeapSize)
fun withAdditionalCordapps(additionalCordapps: Set<TestCordapp>): NodeParameters = copy(additionalCordapps = additionalCordapps) fun withAdditionalCordapps(additionalCordapps: Set<TestCordapp>): NodeParameters = copy(additionalCordapps = additionalCordapps)
fun withRegenerateCordappsOnStart(regenerateCordappsOnStart: Boolean): NodeParameters = copy(regenerateCordappsOnStart = regenerateCordappsOnStart)
fun withFlowOverrides(flowOverrides: Map<Class<out FlowLogic<*>>, Class<out FlowLogic<*>>>): NodeParameters = copy(flowOverrides = flowOverrides) fun withFlowOverrides(flowOverrides: Map<Class<out FlowLogic<*>>, Class<out FlowLogic<*>>>): NodeParameters = copy(flowOverrides = flowOverrides)
constructor( constructor(

View File

@ -7,6 +7,7 @@ import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession import net.corda.core.flows.FlowSession
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.uncheckedCast
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
@ -16,13 +17,15 @@ import net.corda.node.internal.InitiatedFlowFactory
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.node.internal.* import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNodeParameters
import net.corda.testing.node.internal.TestStartedNode
import net.corda.testing.node.internal.newContext
import rx.Observable import rx.Observable
import java.math.BigInteger import java.math.BigInteger
import java.nio.file.Path import java.nio.file.Path
import java.util.concurrent.Future import java.util.concurrent.Future
/** /**
* Immutable builder for configuring a [StartedMockNode] or an [UnstartedMockNode] via [MockNetwork.createNode] and * Immutable builder for configuring a [StartedMockNode] or an [UnstartedMockNode] via [MockNetwork.createNode] and
* [MockNetwork.createUnstartedNode]. Kotlin users can also use the named parameters overloads of those methods which * [MockNetwork.createUnstartedNode]. Kotlin users can also use the named parameters overloads of those methods which
@ -77,6 +80,7 @@ data class MockNodeParameters(
* @property notarySpecs The notaries to use in the mock network. By default you get one mock notary and that is usually sufficient. * @property notarySpecs The notaries to use in the mock network. By default you get one mock notary and that is usually sufficient.
* @property networkParameters The network parameters to be used by all the nodes. [NetworkParameters.notaries] must be * @property networkParameters The network parameters to be used by all the nodes. [NetworkParameters.notaries] must be
* empty as notaries are defined by [notarySpecs]. * empty as notaries are defined by [notarySpecs].
* @property cordappsForAllNodes [TestCordapp]s added to all nodes.
*/ */
@Suppress("unused") @Suppress("unused")
data class MockNetworkParameters( data class MockNetworkParameters(
@ -84,13 +88,32 @@ data class MockNetworkParameters(
val threadPerNode: Boolean = false, val threadPerNode: Boolean = false,
val servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random(), val servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random(),
val notarySpecs: List<MockNetworkNotarySpec> = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME)), val notarySpecs: List<MockNetworkNotarySpec> = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME)),
val networkParameters: NetworkParameters = testNetworkParameters() val networkParameters: NetworkParameters = testNetworkParameters(),
val cordappsForAllNodes: Collection<TestCordapp> = emptyList()
) { ) {
constructor(networkSendManuallyPumped: Boolean,
threadPerNode: Boolean,
servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy,
notarySpecs: List<MockNetworkNotarySpec>,
networkParameters: NetworkParameters
) : this(networkSendManuallyPumped, threadPerNode, servicePeerAllocationStrategy, notarySpecs, networkParameters, emptyList())
fun withNetworkParameters(networkParameters: NetworkParameters): MockNetworkParameters = copy(networkParameters = networkParameters) fun withNetworkParameters(networkParameters: NetworkParameters): MockNetworkParameters = copy(networkParameters = networkParameters)
fun withNetworkSendManuallyPumped(networkSendManuallyPumped: Boolean): MockNetworkParameters = copy(networkSendManuallyPumped = networkSendManuallyPumped) fun withNetworkSendManuallyPumped(networkSendManuallyPumped: Boolean): MockNetworkParameters = copy(networkSendManuallyPumped = networkSendManuallyPumped)
fun withThreadPerNode(threadPerNode: Boolean): MockNetworkParameters = copy(threadPerNode = threadPerNode) fun withThreadPerNode(threadPerNode: Boolean): MockNetworkParameters = copy(threadPerNode = threadPerNode)
fun withServicePeerAllocationStrategy(servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy): MockNetworkParameters = copy(servicePeerAllocationStrategy = servicePeerAllocationStrategy) fun withServicePeerAllocationStrategy(servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy): MockNetworkParameters {
return copy(servicePeerAllocationStrategy = servicePeerAllocationStrategy)
}
fun withNotarySpecs(notarySpecs: List<MockNetworkNotarySpec>): MockNetworkParameters = copy(notarySpecs = notarySpecs) fun withNotarySpecs(notarySpecs: List<MockNetworkNotarySpec>): MockNetworkParameters = copy(notarySpecs = notarySpecs)
fun withCordappsForAllNodes(cordappsForAllNodes: Collection<TestCordapp>): MockNetworkParameters = copy(cordappsForAllNodes = cordappsForAllNodes)
fun copy(networkSendManuallyPumped: Boolean,
threadPerNode: Boolean,
servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy,
notarySpecs: List<MockNetworkNotarySpec>,
networkParameters: NetworkParameters): MockNetworkParameters {
return MockNetworkParameters(networkSendManuallyPumped, threadPerNode, servicePeerAllocationStrategy, notarySpecs, networkParameters, emptyList())
}
} }
/** /**
@ -282,33 +305,37 @@ inline fun <reified F : FlowLogic<*>> StartedMockNode.registerResponderFlow(
* @property notarySpecs The notaries to use in the mock network. By default you get one mock notary and that is usually sufficient. * @property notarySpecs The notaries to use in the mock network. By default you get one mock notary and that is usually sufficient.
* @property networkParameters The network parameters to be used by all the nodes. [NetworkParameters.notaries] must be * @property networkParameters The network parameters to be used by all the nodes. [NetworkParameters.notaries] must be
* empty as notaries are defined by [notarySpecs]. * empty as notaries are defined by [notarySpecs].
* @property cordappsForAllNodes [TestCordapp]s that will be added to each node started by the [MockNetwork].
*/ */
@Suppress("MemberVisibilityCanBePrivate", "CanBeParameter") @Suppress("MemberVisibilityCanBePrivate", "CanBeParameter")
open class MockNetwork( open class MockNetwork(
@Deprecated("cordappPackages does not preserve the original CorDapp's versioning and metadata, which may lead to " +
"misleading results in tests. Use MockNetworkParameters.cordappsForAllNodes instead.")
val cordappPackages: List<String>, val cordappPackages: List<String>,
val defaultParameters: MockNetworkParameters = MockNetworkParameters(), val defaultParameters: MockNetworkParameters = MockNetworkParameters(),
val networkSendManuallyPumped: Boolean = defaultParameters.networkSendManuallyPumped, val networkSendManuallyPumped: Boolean = defaultParameters.networkSendManuallyPumped,
val threadPerNode: Boolean = defaultParameters.threadPerNode, val threadPerNode: Boolean = defaultParameters.threadPerNode,
val servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy, val servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy,
val notarySpecs: List<MockNetworkNotarySpec> = defaultParameters.notarySpecs, val notarySpecs: List<MockNetworkNotarySpec> = defaultParameters.notarySpecs,
val networkParameters: NetworkParameters = defaultParameters.networkParameters, val networkParameters: NetworkParameters = defaultParameters.networkParameters
val cordappsForAllNodes: Collection<TestCordapp> = cordappsForPackages(cordappPackages)) { ) {
@Deprecated("cordappPackages does not preserve the original CorDapp's versioning and metadata, which may lead to " +
"misleading results in tests. Use MockNetworkParameters.cordappsForAllNodes instead.")
@JvmOverloads @JvmOverloads
constructor(cordappPackages: List<String>, parameters: MockNetworkParameters = MockNetworkParameters()) : this(cordappPackages, defaultParameters = parameters) constructor(cordappPackages: List<String>, parameters: MockNetworkParameters = MockNetworkParameters()) : this(cordappPackages, defaultParameters = parameters)
constructor( @JvmOverloads
cordappPackages: List<String>, constructor(parameters: MockNetworkParameters = MockNetworkParameters()) : this(emptyList(), parameters)
defaultParameters: MockNetworkParameters = MockNetworkParameters(),
networkSendManuallyPumped: Boolean = defaultParameters.networkSendManuallyPumped,
threadPerNode: Boolean = defaultParameters.threadPerNode,
servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy,
notarySpecs: List<MockNetworkNotarySpec> = defaultParameters.notarySpecs,
networkParameters: NetworkParameters = defaultParameters.networkParameters
) : this(emptyList(), defaultParameters, networkSendManuallyPumped, threadPerNode, servicePeerAllocationStrategy, notarySpecs, networkParameters, cordappsForAllNodes = cordappsForPackages(cordappPackages))
private val internalMockNetwork: InternalMockNetwork = InternalMockNetwork(defaultParameters, networkSendManuallyPumped, threadPerNode, servicePeerAllocationStrategy, notarySpecs, initialNetworkParameters = networkParameters, cordappsForAllNodes = cordappsForAllNodes) private val internalMockNetwork = InternalMockNetwork(
cordappPackages,
defaultParameters,
networkSendManuallyPumped,
threadPerNode,
servicePeerAllocationStrategy,
notarySpecs,
initialNetworkParameters = networkParameters,
cordappsForAllNodes = uncheckedCast(defaultParameters.cordappsForAllNodes)
)
/** In a mock network, nodes have an incrementing integer ID. Real networks do not have this. Returns the next ID that will be used. */ /** In a mock network, nodes have an incrementing integer ID. Real networks do not have this. Returns the next ID that will be used. */
val nextNodeId get(): Int = internalMockNetwork.nextNodeId val nextNodeId get(): Int = internalMockNetwork.nextNodeId
@ -346,58 +373,18 @@ open class MockNetwork(
* @param entropyRoot The initial entropy value to use when generating keys. Defaults to an (insecure) random value, * @param entropyRoot The initial entropy value to use when generating keys. Defaults to an (insecure) random value,
* but can be overridden to cause nodes to have stable or colliding identity/service keys. * but can be overridden to cause nodes to have stable or colliding identity/service keys.
* @param configOverrides Add/override the default configuration/behaviour of the node * @param configOverrides Add/override the default configuration/behaviour of the node
* @param extraCordappPackages Extra CorDapp packages to add for this node.
*/ */
@JvmOverloads @JvmOverloads
fun createNode(legalName: CordaX500Name? = null, fun createNode(legalName: CordaX500Name? = null,
forcedID: Int? = null, forcedID: Int? = null,
entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
configOverrides: MockNodeConfigOverrides? = null, configOverrides: MockNodeConfigOverrides? = null): StartedMockNode {
extraCordappPackages: List<String> = emptyList()): StartedMockNode { return createNode(MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides))
return createNode(legalName, forcedID, entropyRoot, configOverrides, cordappsForPackages(extraCordappPackages))
}
/**
* Create a started node with the given parameters.
*
* @param legalName The node's legal name.
* @param forcedID A unique identifier for the node.
* @param entropyRoot The initial entropy value to use when generating keys. Defaults to an (insecure) random value,
* but can be overridden to cause nodes to have stable or colliding identity/service keys.
* @param configOverrides Add/override behaviour of the [NodeConfiguration] mock object.
* @param additionalCordapps Additional [TestCordapp]s that this node will have available, in addition to the ones common to all nodes managed by the [MockNetwork].
*/
fun createNode(legalName: CordaX500Name? = null,
forcedID: Int? = null,
entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
configOverrides: MockNodeConfigOverrides? = null,
additionalCordapps: Collection<TestCordapp>): StartedMockNode {
val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, additionalCordapps)
return StartedMockNode.create(internalMockNetwork.createNode(InternalMockNodeParameters(parameters)))
} }
/** Create an unstarted node with the given parameters. **/ /** Create an unstarted node with the given parameters. **/
fun createUnstartedNode(parameters: MockNodeParameters = MockNodeParameters()): UnstartedMockNode = UnstartedMockNode.create(internalMockNetwork.createUnstartedNode(InternalMockNodeParameters(parameters))) fun createUnstartedNode(parameters: MockNodeParameters = MockNodeParameters()): UnstartedMockNode {
return UnstartedMockNode.create(internalMockNetwork.createUnstartedNode(InternalMockNodeParameters(parameters)))
/**
* Create an unstarted node with the given parameters.
*
* @param legalName The node's legal name.
* @param forcedID A unique identifier for the node.
* @param entropyRoot The initial entropy value to use when generating keys. Defaults to an (insecure) random value,
* but can be overridden to cause nodes to have stable or colliding identity/service keys.
* @param configOverrides Add/override behaviour of the [NodeConfiguration] mock object.
* @param extraCordappPackages Extra CorDapp packages to add for this node.
*/
@JvmOverloads
fun createUnstartedNode(legalName: CordaX500Name? = null,
forcedID: Int? = null,
entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
configOverrides: MockNodeConfigOverrides? = null,
extraCordappPackages: List<String> = emptyList()): UnstartedMockNode {
return createUnstartedNode(legalName, forcedID, entropyRoot, configOverrides, cordappsForPackages(extraCordappPackages))
} }
/** /**
@ -408,15 +395,13 @@ open class MockNetwork(
* @param entropyRoot The initial entropy value to use when generating keys. Defaults to an (insecure) random value, * @param entropyRoot The initial entropy value to use when generating keys. Defaults to an (insecure) random value,
* but can be overridden to cause nodes to have stable or colliding identity/service keys. * but can be overridden to cause nodes to have stable or colliding identity/service keys.
* @param configOverrides Add/override behaviour of the [NodeConfiguration] mock object. * @param configOverrides Add/override behaviour of the [NodeConfiguration] mock object.
* @param additionalCordapps Additional [TestCordapp]s that this node will have available, in addition to the ones common to all nodes managed by the [MockNetwork].
*/ */
@JvmOverloads
fun createUnstartedNode(legalName: CordaX500Name? = null, fun createUnstartedNode(legalName: CordaX500Name? = null,
forcedID: Int? = null, forcedID: Int? = null,
entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
configOverrides: MockNodeConfigOverrides? = null, configOverrides: MockNodeConfigOverrides? = null): UnstartedMockNode {
additionalCordapps: Collection<TestCordapp>): UnstartedMockNode { return createUnstartedNode(MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides))
val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, additionalCordapps)
return UnstartedMockNode.create(internalMockNetwork.createUnstartedNode(InternalMockNodeParameters(parameters)))
} }
/** Start all nodes that aren't already started. **/ /** Start all nodes that aren't already started. **/

View File

@ -33,7 +33,6 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.contextTransaction import net.corda.nodeapi.internal.persistence.contextTransaction
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.common.internal.addNotary
import net.corda.testing.core.TestIdentity import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.DEV_ROOT_CA import net.corda.testing.internal.DEV_ROOT_CA
import net.corda.testing.internal.MockCordappProvider import net.corda.testing.internal.MockCordappProvider
@ -76,10 +75,8 @@ open class MockServices private constructor(
) : ServiceHub { ) : ServiceHub {
companion object { companion object {
private fun cordappLoaderForPackages(packages: Iterable<String>, versionInfo: VersionInfo = VersionInfo.UNKNOWN): CordappLoader { private fun cordappLoaderForPackages(packages: Iterable<String>, versionInfo: VersionInfo = VersionInfo.UNKNOWN): CordappLoader {
val cordappPaths = cordappsForPackages(packages).map { TestCordappDirectories.getJarDirectory(it) } return JarScanningCordappLoader.fromJarUrls(cordappsForPackages(packages).map { it.jarFile.toUri().toURL() }, versionInfo)
return JarScanningCordappLoader.fromDirectories(cordappPaths, versionInfo)
} }
/** /**

View File

@ -1,88 +1,41 @@
package net.corda.testing.node package net.corda.testing.node
import net.corda.core.DoNotImplement import net.corda.core.DoNotImplement
import net.corda.core.internal.PLATFORM_VERSION import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.NodeParameters
import net.corda.testing.node.internal.TestCordappImpl import net.corda.testing.node.internal.TestCordappImpl
import net.corda.testing.node.internal.simplifyScanPackages
import java.nio.file.Path
/** /**
* Represents information about a CorDapp. Used to generate CorDapp JARs in tests. * Encapsulates a CorDapp that exists on the current classpath, which can be pulled in for testing. Use [TestCordapp.Factory.findCordapp]
* to locate an existing CorDapp.
*
* @see DriverParameters.cordappsForAllNodes
* @see NodeParameters.additionalCordapps
* @see MockNetworkParameters.cordappsForAllNodes
* @see MockNodeParameters.additionalCordapps
*/ */
@DoNotImplement @DoNotImplement
interface TestCordapp { interface TestCordapp {
/** Returns the name, defaults to "test-name" if not specified. */ /** The package used to find the CorDapp. This may not be the root package of the CorDapp. */
val name: String val scanPackage: String
/** Returns the title, defaults to "test-title" if not specified. */ /** Returns the config for on this CorDapp, defaults to empty if not specified. */
val title: String
/** Returns the version string, defaults to "1.0" if not specified. */
val version: String
/** Returns the vendor string, defaults to "test-vendor" if not specified. */
val vendor: String
/** Returns the target platform version, defaults to the current platform version if not specified. */
val targetVersion: Int
/** Returns the config for this CorDapp, defaults to empty if not specified. */
val config: Map<String, Any> val config: Map<String, Any>
/** Returns the set of package names scanned for this test CorDapp. */
val packages: Set<String>
/** Returns whether the CorDapp should be jar signed. */
val signJar: Boolean
/** Return a copy of this [TestCordapp] but with the specified name. */
fun withName(name: String): TestCordapp
/** Return a copy of this [TestCordapp] but with the specified title. */
fun withTitle(title: String): TestCordapp
/** Return a copy of this [TestCordapp] but with the specified version string. */
fun withVersion(version: String): TestCordapp
/** Return a copy of this [TestCordapp] but with the specified vendor string. */
fun withVendor(vendor: String): TestCordapp
/** Return a copy of this [TestCordapp] but with the specified target platform version. */
fun withTargetVersion(targetVersion: Int): TestCordapp
/** Returns a copy of this [TestCordapp] but with the specified CorDapp config. */ /** Returns a copy of this [TestCordapp] but with the specified CorDapp config. */
fun withConfig(config: Map<String, Any>): TestCordapp fun withConfig(config: Map<String, Any>): TestCordapp
/** Returns a signed copy of this [TestCordapp].
* Optionally can pass in the location of an existing java key store to use */
fun signJar(keyStorePath: Path? = null): TestCordappImpl
class Factory { class Factory {
companion object { companion object {
/** /**
* Create a [TestCordapp] object by scanning the given packages. The meta data on the CorDapp will be the * Scans the current classpath to find the CorDapp that contains the given package. All the CorDapp's metdata present in its
* default values, which can be changed with the wither methods. * MANIFEST are inherited. If more than one location containing the package is found then an exception is thrown. An exception
* is also thrown if no CorDapp is found.
*
* @param scanPackage The package name used to find the CorDapp. This does not need to be the root package.
*/ */
@JvmStatic @JvmStatic
fun fromPackages(vararg packageNames: String): TestCordapp = fromPackages(packageNames.asList()) fun findCordapp(scanPackage: String): TestCordapp = TestCordappImpl(scanPackage = scanPackage, config = emptyMap())
/**
* Create a [TestCordapp] object by scanning the given packages. The meta data on the CorDapp will be the
* default values, which can be changed with the wither methods.
*/
@JvmStatic
fun fromPackages(packageNames: Collection<String>): TestCordapp {
return TestCordappImpl(
name = "test-name",
version = "1",
vendor = "test-vendor",
title = "test-title",
targetVersion = PLATFORM_VERSION,
config = emptyMap(),
packages = simplifyScanPackages(packageNames),
classes = emptySet()
)
}
} }
} }
} }

View File

@ -0,0 +1,141 @@
package net.corda.testing.node.internal
import io.github.classgraph.ClassGraph
import net.corda.core.internal.*
import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.internal.cordapp.set
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.testing.core.internal.JarSignatureTestUtils.generateKey
import net.corda.testing.core.internal.JarSignatureTestUtils.signJar
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.attribute.FileTime
import java.time.Instant
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.jar.Attributes
import java.util.jar.JarFile
import java.util.jar.JarOutputStream
import java.util.jar.Manifest
import java.util.zip.ZipEntry
/**
* Represents a completely custom CorDapp comprising of resources taken from packages on the existing classpath, even including individual
* disparate classes. The CorDapp metadata that's present in the MANIFEST can also be tailored.
*/
data class CustomCordapp(
val packages: Set<String>,
val name: String = "custom-cordapp",
val versionId: Int = 1,
val targetPlatformVersion: Int = PLATFORM_VERSION,
val classes: Set<Class<*>> = emptySet(),
val signingInfo: SigningInfo? = null,
override val config: Map<String, Any> = emptyMap()
) : TestCordappInternal {
init {
require(packages.isNotEmpty() || classes.isNotEmpty()) { "At least one package or class must be specified" }
}
override val jarFile: Path get() = getJarFile(this)
override val scanPackage: String get() = throw UnsupportedOperationException()
override fun withConfig(config: Map<String, Any>): CustomCordapp = copy(config = config)
override fun withOnlyJarContents(): CustomCordapp = CustomCordapp(packages = packages, classes = classes)
fun signed(keyStorePath: Path? = null): CustomCordapp = copy(signingInfo = SigningInfo(keyStorePath))
@VisibleForTesting
internal fun packageAsJar(file: Path) {
val scanResult = ClassGraph()
.whitelistPackages(*packages.toTypedArray())
.whitelistClasses(*classes.map { it.name }.toTypedArray())
.scan()
scanResult.use {
JarOutputStream(file.outputStream()).use { jos ->
jos.addEntry(testEntry(JarFile.MANIFEST_NAME)) {
createTestManifest(name, versionId, targetPlatformVersion).write(jos)
}
// The same resource may be found in different locations (this will happen when running from gradle) so just
// pick the first one found.
scanResult.allResources.asMap().forEach { path, resourceList ->
jos.addEntry(testEntry(path), resourceList[0].open())
}
}
}
}
private fun signJar(jarFile: Path) {
if (signingInfo != null) {
val testKeystore = "_teststore"
val alias = "Test"
val pwd = "secret!"
val keyStorePathToUse = if (signingInfo.keyStorePath != null) {
signingInfo.keyStorePath
} else {
defaultJarSignerDirectory.createDirectories()
if (!(defaultJarSignerDirectory / testKeystore).exists()) {
defaultJarSignerDirectory.generateKey(alias, pwd, "O=Test Company Ltd,OU=Test,L=London,C=GB")
}
defaultJarSignerDirectory
}
val pk = keyStorePathToUse.signJar(jarFile.toString(), alias, pwd)
logger.debug { "Signed Jar: $jarFile with public key $pk" }
} else {
logger.debug { "Unsigned Jar: $jarFile" }
}
}
private fun createTestManifest(name: String, versionId: Int, targetPlatformVersion: Int): Manifest {
val manifest = Manifest()
// Mandatory manifest attribute. If not present, all other entries are silently skipped.
manifest[Attributes.Name.MANIFEST_VERSION] = "1.0"
manifest[CordappImpl.CORDAPP_CONTRACT_NAME] = name
manifest[CordappImpl.CORDAPP_CONTRACT_VERSION] = versionId.toString()
manifest[CordappImpl.CORDAPP_WORKFLOW_NAME] = name
manifest[CordappImpl.CORDAPP_WORKFLOW_VERSION] = versionId.toString()
manifest[CordappImpl.TARGET_PLATFORM_VERSION] = targetPlatformVersion.toString()
return manifest
}
private fun testEntry(name: String): ZipEntry {
return ZipEntry(name).setCreationTime(epochFileTime).setLastAccessTime(epochFileTime).setLastModifiedTime(epochFileTime)
}
data class SigningInfo(val keyStorePath: Path? = null)
companion object {
private val logger = contextLogger()
private val epochFileTime = FileTime.from(Instant.EPOCH)
private val cordappsDirectory: Path
private val defaultJarSignerDirectory: Path
private val whitespace = "\\s".toRegex()
private val cache = ConcurrentHashMap<CustomCordapp, Path>()
init {
val buildDir = Paths.get("build").toAbsolutePath()
val timeDirName = getTimestampAsDirectoryName()
cordappsDirectory = buildDir / "generated-custom-cordapps" / timeDirName
defaultJarSignerDirectory = buildDir / "jar-signer" / timeDirName
}
fun getJarFile(cordapp: CustomCordapp): Path {
// The CorDapp config is external to the jar and so can be ignored here
return cache.computeIfAbsent(cordapp.copy(config = emptyMap())) {
val filename = it.run { "${name.replace(whitespace, "-")}_${versionId}_${targetPlatformVersion}_${UUID.randomUUID()}.jar" }
val jarFile = cordappsDirectory.createDirectories() / filename
it.packageAsJar(jarFile)
it.signJar(jarFile)
logger.debug { "$it packaged into $jarFile" }
jarFile
}
}
}
}

View File

@ -27,7 +27,6 @@ import net.corda.node.internal.NodeWithInfo
import net.corda.node.internal.clientSslOptionsCompatibleWith import net.corda.node.internal.clientSslOptionsCompatibleWith
import net.corda.node.services.Permissions import net.corda.node.services.Permissions
import net.corda.node.services.config.* import net.corda.node.services.config.*
import net.corda.node.services.config.NodeConfiguration.Companion.cordappDirectoriesKey
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
import net.corda.node.utilities.registration.NodeRegistrationHelper import net.corda.node.utilities.registration.NodeRegistrationHelper
import net.corda.nodeapi.internal.DevIdentityGenerator import net.corda.nodeapi.internal.DevIdentityGenerator
@ -49,8 +48,6 @@ import net.corda.testing.driver.internal.OutOfProcessImpl
import net.corda.testing.internal.stubs.CertificateStoreStubs import net.corda.testing.internal.stubs.CertificateStoreStubs
import net.corda.testing.node.ClusterSpec import net.corda.testing.node.ClusterSpec
import net.corda.testing.node.NotarySpec import net.corda.testing.node.NotarySpec
import net.corda.testing.node.TestCordapp
import net.corda.testing.node.internal.DriverDSLImpl.Companion.cordappsInCurrentAndAdditionalPackages
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import rx.Subscription import rx.Subscription
@ -73,6 +70,7 @@ import java.util.concurrent.TimeoutException
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.collections.HashMap import kotlin.collections.HashMap
import kotlin.collections.HashSet
import kotlin.concurrent.thread import kotlin.concurrent.thread
import net.corda.nodeapi.internal.config.User as InternalUser import net.corda.nodeapi.internal.config.User as InternalUser
@ -85,20 +83,21 @@ class DriverDSLImpl(
val isDebug: Boolean, val isDebug: Boolean,
val startNodesInProcess: Boolean, val startNodesInProcess: Boolean,
val waitForAllNodesToFinish: Boolean, val waitForAllNodesToFinish: Boolean,
val extraCordappPackagesToScan: List<String>,
val jmxPolicy: JmxPolicy, val jmxPolicy: JmxPolicy,
val notarySpecs: List<NotarySpec>, val notarySpecs: List<NotarySpec>,
val compatibilityZone: CompatibilityZoneParams?, val compatibilityZone: CompatibilityZoneParams?,
val networkParameters: NetworkParameters, val networkParameters: NetworkParameters,
val notaryCustomOverrides: Map<String, Any?>, val notaryCustomOverrides: Map<String, Any?>,
val inMemoryDB: Boolean, val inMemoryDB: Boolean,
val cordappsForAllNodes: Collection<TestCordapp>, val cordappsForAllNodes: Collection<TestCordappInternal>?
val signCordapps: Boolean
) : InternalDriverDSL { ) : InternalDriverDSL {
private var _executorService: ScheduledExecutorService? = null private var _executorService: ScheduledExecutorService? = null
val executorService get() = _executorService!! val executorService get() = _executorService!!
private var _shutdownManager: ShutdownManager? = null private var _shutdownManager: ShutdownManager? = null
override val shutdownManager get() = _shutdownManager!! override val shutdownManager get() = _shutdownManager!!
private lateinit var extraCustomCordapps: Set<CustomCordapp>
// Map from a nodes legal name to an observable emitting the number of nodes in its network map. // Map from a nodes legal name to an observable emitting the number of nodes in its network map.
private val networkVisibilityController = NetworkVisibilityController() private val networkVisibilityController = NetworkVisibilityController()
/** /**
@ -198,7 +197,7 @@ class DriverDSLImpl(
return registrationFuture.flatMap { return registrationFuture.flatMap {
networkMapAvailability.flatMap { networkMapAvailability.flatMap {
// But starting the node proper does require the network map // But starting the node proper does require the network map
startRegisteredNode(name, it, parameters, p2pAddress, signCordapps) startRegisteredNode(name, it, parameters, p2pAddress)
} }
} }
} }
@ -206,8 +205,7 @@ class DriverDSLImpl(
private fun startRegisteredNode(name: CordaX500Name, private fun startRegisteredNode(name: CordaX500Name,
localNetworkMap: LocalNetworkMap?, localNetworkMap: LocalNetworkMap?,
parameters: NodeParameters, parameters: NodeParameters,
p2pAddress: NetworkHostAndPort = portAllocation.nextHostAndPort(), p2pAddress: NetworkHostAndPort = portAllocation.nextHostAndPort()): CordaFuture<NodeHandle> {
signCordapps: Boolean = false): CordaFuture<NodeHandle> {
val rpcAddress = portAllocation.nextHostAndPort() val rpcAddress = portAllocation.nextHostAndPort()
val rpcAdminAddress = portAllocation.nextHostAndPort() val rpcAdminAddress = portAllocation.nextHostAndPort()
val webAddress = portAllocation.nextHostAndPort() val webAddress = portAllocation.nextHostAndPort()
@ -229,7 +227,7 @@ class DriverDSLImpl(
} }
val flowOverrideConfig = FlowOverrideConfig(parameters.flowOverrides.map { FlowOverride(it.key.canonicalName, it.value.canonicalName) }) val flowOverrideConfig = FlowOverrideConfig(parameters.flowOverrides.map { FlowOverride(it.key.canonicalName, it.value.canonicalName) })
val overrides = configOf( val overrides = configOf(
NodeConfiguration::myLegalName.name to name.toString(), NodeConfiguration::myLegalName.name to name.toString(),
NodeConfiguration::p2pAddress.name to p2pAddress.toString(), NodeConfiguration::p2pAddress.name to p2pAddress.toString(),
@ -245,7 +243,7 @@ class DriverDSLImpl(
allowMissingConfig = true, allowMissingConfig = true,
configOverrides = if (overrides.hasPath("devMode")) overrides else overrides + mapOf("devMode" to true) configOverrides = if (overrides.hasPath("devMode")) overrides else overrides + mapOf("devMode" to true)
)).checkAndOverrideForInMemoryDB() )).checkAndOverrideForInMemoryDB()
return startNodeInternal(config, webAddress, localNetworkMap, parameters, signCordapps) return startNodeInternal(config, webAddress, localNetworkMap, parameters)
} }
private fun startNodeRegistration( private fun startNodeRegistration(
@ -333,6 +331,9 @@ class DriverDSLImpl(
require(networkParameters.notaries.isEmpty()) { "Define notaries using notarySpecs" } require(networkParameters.notaries.isEmpty()) { "Define notaries using notarySpecs" }
_executorService = Executors.newScheduledThreadPool(2, ThreadFactoryBuilder().setNameFormat("driver-pool-thread-%d").build()) _executorService = Executors.newScheduledThreadPool(2, ThreadFactoryBuilder().setNameFormat("driver-pool-thread-%d").build())
_shutdownManager = ShutdownManager(executorService) _shutdownManager = ShutdownManager(executorService)
extraCustomCordapps = cordappsForPackages(extraCordappPackagesToScan + getCallerPackage())
val notaryInfosFuture = if (compatibilityZone == null) { val notaryInfosFuture = if (compatibilityZone == null) {
// If no CZ is specified then the driver does the generation of the network parameters and the copying of the // If no CZ is specified then the driver does the generation of the network parameters and the copying of the
// node info files. // node info files.
@ -369,6 +370,27 @@ class DriverDSLImpl(
} }
} }
/**
* Get the package of the caller to the driver so that it can be added to the list of packages the nodes will scan.
* This makes the driver automatically pick the CorDapp module that it's run from.
*
* This returns List<String> rather than String? to make it easier to bolt onto extraCordappPackagesToScan.
*/
private fun getCallerPackage(): List<String> {
if (cordappsForAllNodes != null) {
// We turn this feature off if cordappsForAllNodes is being used
return emptyList()
}
val stackTrace = Throwable().stackTrace
val index = stackTrace.indexOfLast { it.className == "net.corda.testing.driver.Driver" }
return if (index == -1) {
// In this case we're dealing with the the RPCDriver or one of it's cousins which are internal and we don't care about them
emptyList()
} else {
listOf(Class.forName(stackTrace[index + 1].className).packageName)
}
}
private fun startNotaryIdentityGeneration(): CordaFuture<List<NotaryInfo>> { private fun startNotaryIdentityGeneration(): CordaFuture<List<NotaryInfo>> {
return executorService.fork { return executorService.fork {
notarySpecs.map { spec -> notarySpecs.map { spec ->
@ -531,13 +553,12 @@ class DriverDSLImpl(
} }
} }
private fun startNodeInternal(specifiedConfig: NodeConfig, private fun startNodeInternal(config: NodeConfig,
webAddress: NetworkHostAndPort, webAddress: NetworkHostAndPort,
localNetworkMap: LocalNetworkMap?, localNetworkMap: LocalNetworkMap?,
parameters: NodeParameters, parameters: NodeParameters): CordaFuture<NodeHandle> {
signCordapps: Boolean = false): CordaFuture<NodeHandle> { val visibilityHandle = networkVisibilityController.register(config.corda.myLegalName)
val visibilityHandle = networkVisibilityController.register(specifiedConfig.corda.myLegalName) val baseDirectory = config.corda.baseDirectory.createDirectories()
val baseDirectory = specifiedConfig.corda.baseDirectory.createDirectories()
localNetworkMap?.networkParametersCopier?.install(baseDirectory) localNetworkMap?.networkParametersCopier?.install(baseDirectory)
localNetworkMap?.nodeInfosCopier?.addConfig(baseDirectory) localNetworkMap?.nodeInfosCopier?.addConfig(baseDirectory)
@ -546,24 +567,13 @@ class DriverDSLImpl(
visibilityHandle.close() visibilityHandle.close()
} }
val useHTTPS = specifiedConfig.typesafe.run { hasPath("useHTTPS") && getBoolean("useHTTPS") } val useHTTPS = config.typesafe.run { hasPath("useHTTPS") && getBoolean("useHTTPS") }
val existingCorDappDirectories = if (parameters.regenerateCordappsOnStart) { TestCordappInternal.installCordapps(
emptyList() baseDirectory,
} else if (specifiedConfig.typesafe.hasPath(cordappDirectoriesKey)) { parameters.additionalCordapps.mapTo(HashSet()) { it as TestCordappInternal },
specifiedConfig.typesafe.getStringList(cordappDirectoriesKey) extraCustomCordapps + (cordappsForAllNodes ?: emptySet())
} else { )
emptyList()
}
// Instead of using cordappsForAllNodes we get only these that are missing from additionalCordapps
// This way we prevent errors when we want the same CordApp but with different config
val appOverrides = parameters.additionalCordapps.map { it.name to it.version }.toSet()
val baseCordapps = cordappsForAllNodes.filter { !appOverrides.contains(it.name to it.version) }
val cordappDirectories = existingCorDappDirectories + (baseCordapps + parameters.additionalCordapps).map { TestCordappDirectories.getJarDirectory(it, signJar = signCordapps).toString() }
val config = NodeConfig(specifiedConfig.typesafe.withValue(cordappDirectoriesKey, ConfigValueFactory.fromIterable(cordappDirectories.toSet())))
if (parameters.startInSameProcess ?: startNodesInProcess) { if (parameters.startInSameProcess ?: startNodesInProcess) {
val nodeAndThreadFuture = startInProcessNode(executorService, config) val nodeAndThreadFuture = startInProcessNode(executorService, config)
@ -685,14 +695,6 @@ class DriverDSLImpl(
private fun <A> oneOf(array: Array<A>) = array[Random().nextInt(array.size)] private fun <A> oneOf(array: Array<A>) = array[Random().nextInt(array.size)]
fun cordappsInCurrentAndAdditionalPackages(packagesToScan: Collection<String> = emptySet()): List<TestCordapp> {
return cordappsForPackages(getCallerPackage() + packagesToScan)
}
fun cordappsInCurrentAndAdditionalPackages(firstPackage: String, vararg otherPackages: String): List<TestCordapp> {
return cordappsInCurrentAndAdditionalPackages(otherPackages.asList() + firstPackage)
}
private fun startInProcessNode( private fun startInProcessNode(
executorService: ScheduledExecutorService, executorService: ScheduledExecutorService,
config: NodeConfig config: NodeConfig
@ -818,22 +820,6 @@ class DriverDSLImpl(
private operator fun Config.plus(property: Pair<String, Any>) = withValue(property.first, ConfigValueFactory.fromAnyRef(property.second)) private operator fun Config.plus(property: Pair<String, Any>) = withValue(property.first, ConfigValueFactory.fromAnyRef(property.second))
/**
* Get the package of the caller to the driver so that it can be added to the list of packages the nodes will scan.
* This makes the driver automatically pick the CorDapp module that it's run from.
*
* This returns List<String> rather than String? to make it easier to bolt onto extraCordappPackagesToScan.
*/
private fun getCallerPackage(): List<String> {
val stackTrace = Throwable().stackTrace
val index = stackTrace.indexOfLast { it.className == "net.corda.testing.driver.Driver" }
// In this case we're dealing with the the RPCDriver or one of it's cousins which are internal and we don't care about them
if (index == -1) return emptyList()
val callerPackage = Class.forName(stackTrace[index + 1].className).`package`
?: throw IllegalStateException("Function instantiating driver must be defined in a package.")
return listOf(callerPackage.name)
}
/** /**
* We have an alternative way of specifying classpath for spawned process: by using "-cp" option. So duplicating the setting of this * We have an alternative way of specifying classpath for spawned process: by using "-cp" option. So duplicating the setting of this
* rather long string is un-necessary and can be harmful on Windows. * rather long string is un-necessary and can be harmful on Windows.
@ -997,14 +983,14 @@ fun <DI : DriverDSL, D : InternalDriverDSL, A> genericDriver(
isDebug = defaultParameters.isDebug, isDebug = defaultParameters.isDebug,
startNodesInProcess = defaultParameters.startNodesInProcess, startNodesInProcess = defaultParameters.startNodesInProcess,
waitForAllNodesToFinish = defaultParameters.waitForAllNodesToFinish, waitForAllNodesToFinish = defaultParameters.waitForAllNodesToFinish,
extraCordappPackagesToScan = defaultParameters.extraCordappPackagesToScan,
jmxPolicy = defaultParameters.jmxPolicy, jmxPolicy = defaultParameters.jmxPolicy,
notarySpecs = defaultParameters.notarySpecs, notarySpecs = defaultParameters.notarySpecs,
compatibilityZone = null, compatibilityZone = null,
networkParameters = defaultParameters.networkParameters, networkParameters = defaultParameters.networkParameters,
notaryCustomOverrides = defaultParameters.notaryCustomOverrides, notaryCustomOverrides = defaultParameters.notaryCustomOverrides,
inMemoryDB = defaultParameters.inMemoryDB, inMemoryDB = defaultParameters.inMemoryDB,
cordappsForAllNodes = defaultParameters.cordappsForAllNodes(), cordappsForAllNodes = uncheckedCast(defaultParameters.cordappsForAllNodes)
signCordapps = false
) )
) )
val shutdownHook = addShutdownHook(driverDsl::shutdown) val shutdownHook = addShutdownHook(driverDsl::shutdown)
@ -1089,6 +1075,7 @@ fun <A> internalDriver(
systemProperties: Map<String, String> = DriverParameters().systemProperties, systemProperties: Map<String, String> = DriverParameters().systemProperties,
useTestClock: Boolean = DriverParameters().useTestClock, useTestClock: Boolean = DriverParameters().useTestClock,
startNodesInProcess: Boolean = DriverParameters().startNodesInProcess, startNodesInProcess: Boolean = DriverParameters().startNodesInProcess,
extraCordappPackagesToScan: List<String> = DriverParameters().extraCordappPackagesToScan,
waitForAllNodesToFinish: Boolean = DriverParameters().waitForAllNodesToFinish, waitForAllNodesToFinish: Boolean = DriverParameters().waitForAllNodesToFinish,
notarySpecs: List<NotarySpec> = DriverParameters().notarySpecs, notarySpecs: List<NotarySpec> = DriverParameters().notarySpecs,
jmxPolicy: JmxPolicy = DriverParameters().jmxPolicy, jmxPolicy: JmxPolicy = DriverParameters().jmxPolicy,
@ -1096,8 +1083,7 @@ fun <A> internalDriver(
compatibilityZone: CompatibilityZoneParams? = null, compatibilityZone: CompatibilityZoneParams? = null,
notaryCustomOverrides: Map<String, Any?> = DriverParameters().notaryCustomOverrides, notaryCustomOverrides: Map<String, Any?> = DriverParameters().notaryCustomOverrides,
inMemoryDB: Boolean = DriverParameters().inMemoryDB, inMemoryDB: Boolean = DriverParameters().inMemoryDB,
cordappsForAllNodes: Collection<TestCordapp> = DriverParameters().cordappsForAllNodes(), cordappsForAllNodes: Collection<TestCordappInternal>? = null,
signCordapps: Boolean = false,
dsl: DriverDSLImpl.() -> A dsl: DriverDSLImpl.() -> A
): A { ): A {
return genericDriver( return genericDriver(
@ -1110,14 +1096,14 @@ fun <A> internalDriver(
isDebug = isDebug, isDebug = isDebug,
startNodesInProcess = startNodesInProcess, startNodesInProcess = startNodesInProcess,
waitForAllNodesToFinish = waitForAllNodesToFinish, waitForAllNodesToFinish = waitForAllNodesToFinish,
extraCordappPackagesToScan = extraCordappPackagesToScan,
notarySpecs = notarySpecs, notarySpecs = notarySpecs,
jmxPolicy = jmxPolicy, jmxPolicy = jmxPolicy,
compatibilityZone = compatibilityZone, compatibilityZone = compatibilityZone,
networkParameters = networkParameters, networkParameters = networkParameters,
notaryCustomOverrides = notaryCustomOverrides, notaryCustomOverrides = notaryCustomOverrides,
inMemoryDB = inMemoryDB, inMemoryDB = inMemoryDB,
cordappsForAllNodes = cordappsForAllNodes, cordappsForAllNodes = cordappsForAllNodes
signCordapps = signCordapps
), ),
coerce = { it }, coerce = { it },
dsl = dsl dsl = dsl
@ -1137,9 +1123,6 @@ private fun Config.toNodeOnly(): Config {
return if (hasPath("webAddress")) withoutPath("webAddress").withoutPath("useHTTPS") else this return if (hasPath("webAddress")) withoutPath("webAddress").withoutPath("useHTTPS") else this
} }
internal fun DriverParameters.cordappsForAllNodes(): Collection<TestCordapp> = cordappsForAllNodes
?: cordappsInCurrentAndAdditionalPackages(extraCordappPackagesToScan)
fun DriverDSL.startNode(providedName: CordaX500Name, devMode: Boolean, parameters: NodeParameters = NodeParameters()): CordaFuture<NodeHandle> { fun DriverDSL.startNode(providedName: CordaX500Name, devMode: Boolean, parameters: NodeParameters = NodeParameters()): CordaFuture<NodeHandle> {
val customOverrides = if (!devMode) { val customOverrides = if (!devMode) {
val nodeDir = baseDirectory(providedName) val nodeDir = baseDirectory(providedName)

View File

@ -92,7 +92,7 @@ data class InternalMockNodeParameters(
val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
val configOverrides: MockNodeConfigOverrides? = null, val configOverrides: MockNodeConfigOverrides? = null,
val version: VersionInfo = MOCK_VERSION_INFO, val version: VersionInfo = MOCK_VERSION_INFO,
val additionalCordapps: Collection<TestCordapp>? = null, val additionalCordapps: Collection<TestCordappInternal> = emptyList(),
val flowManager: MockNodeFlowManager = MockNodeFlowManager()) { val flowManager: MockNodeFlowManager = MockNodeFlowManager()) {
constructor(mockNodeParameters: MockNodeParameters) : this( constructor(mockNodeParameters: MockNodeParameters) : this(
mockNodeParameters.forcedID, mockNodeParameters.forcedID,
@ -100,7 +100,7 @@ data class InternalMockNodeParameters(
mockNodeParameters.entropyRoot, mockNodeParameters.entropyRoot,
mockNodeParameters.configOverrides, mockNodeParameters.configOverrides,
MOCK_VERSION_INFO, MOCK_VERSION_INFO,
mockNodeParameters.additionalCordapps uncheckedCast(mockNodeParameters.additionalCordapps)
) )
} }
@ -141,15 +141,17 @@ interface TestStartedNode {
fun <T : FlowLogic<*>> registerInitiatedFlow(initiatingFlowClass: Class<out FlowLogic<*>>, initiatedFlowClass: Class<T>, track: Boolean = false): Observable<T> fun <T : FlowLogic<*>> registerInitiatedFlow(initiatingFlowClass: Class<out FlowLogic<*>>, initiatedFlowClass: Class<T>, track: Boolean = false): Observable<T>
} }
open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParameters(), open class InternalMockNetwork(cordappPackages: List<String> = emptyList(),
// TODO InternalMockNetwork does not need MockNetworkParameters
defaultParameters: MockNetworkParameters = MockNetworkParameters(),
val networkSendManuallyPumped: Boolean = defaultParameters.networkSendManuallyPumped, val networkSendManuallyPumped: Boolean = defaultParameters.networkSendManuallyPumped,
val threadPerNode: Boolean = defaultParameters.threadPerNode, val threadPerNode: Boolean = defaultParameters.threadPerNode,
servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy, servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy,
val notarySpecs: List<MockNetworkNotarySpec> = defaultParameters.notarySpecs, val notarySpecs: List<MockNetworkNotarySpec> = defaultParameters.notarySpecs,
val testDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()), val testDirectory: Path = Paths.get("build") / "mock-network" / getTimestampAsDirectoryName(),
initialNetworkParameters: NetworkParameters = testNetworkParameters(), initialNetworkParameters: NetworkParameters = testNetworkParameters(),
val defaultFactory: (MockNodeArgs) -> MockNode = { args -> MockNode(args) }, val defaultFactory: (MockNodeArgs) -> MockNode = { args -> MockNode(args) },
val cordappsForAllNodes: Collection<TestCordapp> = emptySet(), cordappsForAllNodes: Collection<TestCordappInternal> = emptySet(),
val autoVisibleNodes: Boolean = true) : AutoCloseable { val autoVisibleNodes: Boolean = true) : AutoCloseable {
var networkParameters: NetworkParameters = initialNetworkParameters var networkParameters: NetworkParameters = initialNetworkParameters
@ -174,6 +176,7 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
private val _nodes = mutableListOf<MockNode>() private val _nodes = mutableListOf<MockNode>()
private val serializationEnv = checkNotNull(setDriverSerialization()) { "Using more than one mock network simultaneously is not supported." } private val serializationEnv = checkNotNull(setDriverSerialization()) { "Using more than one mock network simultaneously is not supported." }
private val sharedUserCount = AtomicInteger(0) private val sharedUserCount = AtomicInteger(0)
private val combinedCordappsForAllNodes = cordappsForPackages(cordappPackages) + cordappsForAllNodes
/** A read only view of the current set of nodes. */ /** A read only view of the current set of nodes. */
val nodes: List<MockNode> get() = _nodes val nodes: List<MockNode> get() = _nodes
@ -466,12 +469,11 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
doReturn(parameters.legalName ?: CordaX500Name("Mock Company $id", "London", "GB")).whenever(it).myLegalName doReturn(parameters.legalName ?: CordaX500Name("Mock Company $id", "London", "GB")).whenever(it).myLegalName
doReturn(makeTestDataSourceProperties("node_${id}_net_$networkId")).whenever(it).dataSourceProperties doReturn(makeTestDataSourceProperties("node_${id}_net_$networkId")).whenever(it).dataSourceProperties
doReturn(emptyList<SecureHash>()).whenever(it).extraNetworkMapKeys doReturn(emptyList<SecureHash>()).whenever(it).extraNetworkMapKeys
doReturn(listOf(baseDirectory / "cordapps")).whenever(it).cordappDirectories
parameters.configOverrides?.applyMockNodeOverrides(it) parameters.configOverrides?.applyMockNodeOverrides(it)
} }
val cordapps = (parameters.additionalCordapps ?: emptySet()) + cordappsForAllNodes TestCordappInternal.installCordapps(baseDirectory, parameters.additionalCordapps.toSet(), combinedCordappsForAllNodes)
val cordappDirectories = cordapps.map { TestCordappDirectories.getJarDirectory(it) }.distinct()
doReturn(cordappDirectories).whenever(config).cordappDirectories
val node = nodeFactory(MockNodeArgs(config, this, id, parameters.entropyRoot, parameters.version, flowManager = parameters.flowManager)) val node = nodeFactory(MockNodeArgs(config, this, id, parameters.entropyRoot, parameters.version, flowManager = parameters.flowManager))
_nodes += node _nodes += node

View File

@ -29,11 +29,14 @@ import net.corda.testing.node.testContext
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import rx.Observable import rx.Observable
import rx.subjects.AsyncSubject import rx.subjects.AsyncSubject
import java.io.InputStream
import java.net.Socket import java.net.Socket
import java.net.SocketException import java.net.SocketException
import java.time.Duration import java.time.Duration
import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry
private val log = LoggerFactory.getLogger("net.corda.testing.internal.InternalTestUtils") private val log = LoggerFactory.getLogger("net.corda.testing.internal.InternalTestUtils")
@ -152,3 +155,14 @@ private class DriverSerializationEnvironment : SerializationEnvironment by creat
inVMExecutors.remove(this) inVMExecutors.remove(this)
} }
} }
/** Add a new entry using the entire remaining bytes of [input] for the entry content. [input] will be closed at the end. */
fun JarOutputStream.addEntry(entry: ZipEntry, input: InputStream) {
addEntry(entry) { input.use { it.copyTo(this) } }
}
inline fun JarOutputStream.addEntry(entry: ZipEntry, write: () -> Unit) {
putNextEntry(entry)
write()
closeEntry()
}

View File

@ -1,6 +1,5 @@
package net.corda.testing.node.internal package net.corda.testing.node.internal
import com.typesafe.config.ConfigValueFactory
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.PLATFORM_VERSION import net.corda.core.internal.PLATFORM_VERSION
@ -121,16 +120,10 @@ constructor(private val cordappPackages: List<String> = emptyList(), private val
) + configOverrides ) + configOverrides
) )
val cordapps = cordappsForPackages(getCallerPackage(NodeBasedTest::class)?.let { cordappPackages + it } val customCordapps = cordappsForPackages(getCallerPackage(NodeBasedTest::class)?.let { cordappPackages + it } ?: cordappPackages)
?: cordappPackages) TestCordappInternal.installCordapps(baseDirectory, emptySet(), customCordapps)
val existingCorDappDirectoriesOption = if (config.hasPath(NodeConfiguration.cordappDirectoriesKey)) config.getStringList(NodeConfiguration.cordappDirectoriesKey) else emptyList() val parsedConfig = config.parseAsNodeConfiguration().value()
val cordappDirectories = existingCorDappDirectoriesOption + cordapps.map { TestCordappDirectories.getJarDirectory(it).toString() }
val specificConfig = config.withValue(NodeConfiguration.cordappDirectoriesKey, ConfigValueFactory.fromIterable(cordappDirectories.toSet()))
val parsedConfig = specificConfig.parseAsNodeConfiguration().value()
defaultNetworkParameters.install(baseDirectory) defaultNetworkParameters.install(baseDirectory)
val node = InProcessNode(parsedConfig, MOCK_VERSION_INFO.copy(platformVersion = platformVersion), flowManager = flowManager) val node = InProcessNode(parsedConfig, MOCK_VERSION_INFO.copy(platformVersion = platformVersion), flowManager = flowManager)

View File

@ -32,9 +32,7 @@ import net.corda.testing.driver.internal.incrementalPortAllocation
import net.corda.testing.internal.TestingNamedCacheFactory import net.corda.testing.internal.TestingNamedCacheFactory
import net.corda.testing.internal.fromUserList import net.corda.testing.internal.fromUserList
import net.corda.testing.node.NotarySpec import net.corda.testing.node.NotarySpec
import net.corda.testing.node.TestCordapp
import net.corda.testing.node.User import net.corda.testing.node.User
import net.corda.testing.node.internal.DriverDSLImpl.Companion.cordappsInCurrentAndAdditionalPackages
import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.api.core.TransportConfiguration import org.apache.activemq.artemis.api.core.TransportConfiguration
import org.apache.activemq.artemis.api.core.client.ActiveMQClient import org.apache.activemq.artemis.api.core.client.ActiveMQClient
@ -108,20 +106,21 @@ private val globalDebugPortAllocation = incrementalPortAllocation(5005)
fun <A> rpcDriver( fun <A> rpcDriver(
isDebug: Boolean = false, isDebug: Boolean = false,
driverDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()), driverDirectory: Path = Paths.get("build") / "rpc-driver" / getTimestampAsDirectoryName(),
portAllocation: PortAllocation = globalPortAllocation, portAllocation: PortAllocation = globalPortAllocation,
debugPortAllocation: PortAllocation = globalDebugPortAllocation, debugPortAllocation: PortAllocation = globalDebugPortAllocation,
systemProperties: Map<String, String> = emptyMap(), systemProperties: Map<String, String> = emptyMap(),
useTestClock: Boolean = false, useTestClock: Boolean = false,
startNodesInProcess: Boolean = false, startNodesInProcess: Boolean = false,
waitForNodesToFinish: Boolean = false, waitForNodesToFinish: Boolean = false,
extraCordappPackagesToScan: List<String> = emptyList(),
notarySpecs: List<NotarySpec> = emptyList(), notarySpecs: List<NotarySpec> = emptyList(),
externalTrace: Trace? = null, externalTrace: Trace? = null,
@Suppress("DEPRECATION") jmxPolicy: JmxPolicy = JmxPolicy(), @Suppress("DEPRECATION") jmxPolicy: JmxPolicy = JmxPolicy(),
networkParameters: NetworkParameters = testNetworkParameters(), networkParameters: NetworkParameters = testNetworkParameters(),
notaryCustomOverrides: Map<String, Any?> = emptyMap(), notaryCustomOverrides: Map<String, Any?> = emptyMap(),
inMemoryDB: Boolean = true, inMemoryDB: Boolean = true,
cordappsForAllNodes: Collection<TestCordapp> = cordappsInCurrentAndAdditionalPackages(), cordappsForAllNodes: Collection<TestCordappInternal>? = null,
dsl: RPCDriverDSL.() -> A dsl: RPCDriverDSL.() -> A
): A { ): A {
return genericDriver( return genericDriver(
@ -135,14 +134,14 @@ fun <A> rpcDriver(
isDebug = isDebug, isDebug = isDebug,
startNodesInProcess = startNodesInProcess, startNodesInProcess = startNodesInProcess,
waitForAllNodesToFinish = waitForNodesToFinish, waitForAllNodesToFinish = waitForNodesToFinish,
extraCordappPackagesToScan = extraCordappPackagesToScan,
notarySpecs = notarySpecs, notarySpecs = notarySpecs,
jmxPolicy = jmxPolicy, jmxPolicy = jmxPolicy,
compatibilityZone = null, compatibilityZone = null,
networkParameters = networkParameters, networkParameters = networkParameters,
notaryCustomOverrides = notaryCustomOverrides, notaryCustomOverrides = notaryCustomOverrides,
inMemoryDB = inMemoryDB, inMemoryDB = inMemoryDB,
cordappsForAllNodes = cordappsForAllNodes, cordappsForAllNodes = cordappsForAllNodes
signCordapps = false
), externalTrace ), externalTrace
), ),
coerce = { it }, coerce = { it },

View File

@ -1,64 +0,0 @@
package net.corda.testing.node.internal
import com.typesafe.config.ConfigValueFactory
import net.corda.core.crypto.sha256
import net.corda.core.internal.*
import net.corda.core.utilities.debug
import net.corda.core.utilities.loggerFor
import net.corda.testing.core.internal.JarSignatureTestUtils.generateKey
import net.corda.testing.core.internal.JarSignatureTestUtils.signJar
import net.corda.testing.node.TestCordapp
import java.nio.file.Path
import java.nio.file.Paths
import java.util.*
import java.util.concurrent.ConcurrentHashMap
object TestCordappDirectories {
private val logger = loggerFor<TestCordappDirectories>()
private val whitespace = "\\s".toRegex()
private val testCordappsCache = ConcurrentHashMap<TestCordappImpl, Path>()
//TODO In future, we may wish to associate a signer attribute to TestCordapp interface itself, and trigger signing from that.
fun getJarDirectory(cordapp: TestCordapp, cordappsDirectory: Path = defaultCordappsDirectory, signJar: Boolean = false): Path {
cordapp as TestCordappImpl
return testCordappsCache.computeIfAbsent(cordapp) {
val configString = ConfigValueFactory.fromMap(cordapp.config).toConfig().root().render()
val filename = cordapp.run {
val uniqueScanString = if (packages.size == 1 && classes.isEmpty() && config.isEmpty()) {
packages.first()
} else {
"$packages$classes$configString".toByteArray().sha256().toString()
}
"${name}_${vendor}_${title}_${version}_${targetVersion}_$uniqueScanString".replace(whitespace, "-")
}
val cordappDir = cordappsDirectory / UUID.randomUUID().toString()
val configDir = (cordappDir / "config").createDirectories()
val jarFile = cordappDir / "$filename.jar"
cordapp.packageAsJar(jarFile)
if (signJar || cordapp.signJar) {
val testKeystore = "_teststore"
val alias = "Test"
val pwd = "secret!"
if (!(cordappsDirectory / testKeystore).exists() && (cordapp.keyStorePath == null)) {
cordappsDirectory.generateKey(alias, pwd, "O=Test Company Ltd,OU=Test,L=London,C=GB")
}
val keyStorePathToUse = cordapp.keyStorePath ?: cordappsDirectory
(keyStorePathToUse / testKeystore).copyTo(cordappDir / testKeystore)
val pk = cordappDir.signJar("$filename.jar", alias, pwd)
logger.debug { "Signed Jar: $cordappDir/$filename.jar with public key $pk" }
} else logger.debug { "Unsigned Jar: $cordappDir/$filename.jar" }
(configDir / "$filename.conf").writeText(configString)
logger.debug { "$cordapp packaged into $jarFile" }
cordappDir
}
}
private val defaultCordappsDirectory: Path by lazy {
val cordappsDirectory = Paths.get("build").toAbsolutePath() / "generated-test-cordapps" / getTimestampAsDirectoryName()
logger.info("Initialising generated test CorDapps directory in $cordappsDirectory")
cordappsDirectory.deleteRecursively()
cordappsDirectory.createDirectories()
}
}

View File

@ -1,35 +1,99 @@
package net.corda.testing.node.internal package net.corda.testing.node.internal
import io.github.classgraph.ClassGraph
import net.corda.core.internal.*
import net.corda.core.utilities.contextLogger
import net.corda.testing.node.TestCordapp import net.corda.testing.node.TestCordapp
import org.apache.commons.lang.SystemUtils
import java.nio.file.Path import java.nio.file.Path
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import kotlin.streams.toList
data class TestCordappImpl(override val name: String, /**
override val version: String, * Implementation of the public [TestCordapp] API.
override val vendor: String, *
override val title: String, * As described in [TestCordapp.Factory.findCordapp], this represents a single CorDapp jar on the current classpath. The [scanPackage] may
override val targetVersion: Int, * be for an external dependency to the project that's using this API, in which case that dependency jar is referenced as is. On the other hand,
override val config: Map<String, Any>, * the [scanPackage] may reference a gradle CorDapp project on the local system. In this scenerio the project's "jar" task is executed to
override val packages: Set<String>, * build the CorDapp jar. This allows us to inherit the CorDapp's MANIFEST information without having to do any extra processing.
override val signJar: Boolean = false, */
val keyStorePath: Path? = null, data class TestCordappImpl(override val scanPackage: String, override val config: Map<String, Any>) : TestCordappInternal {
val classes: Set<Class<*>>
) : TestCordapp {
override fun withName(name: String): TestCordappImpl = copy(name = name)
override fun withVersion(version: String): TestCordappImpl = copy(version = version)
override fun withVendor(vendor: String): TestCordappImpl = copy(vendor = vendor)
override fun withTitle(title: String): TestCordappImpl = copy(title = title)
override fun withTargetVersion(targetVersion: Int): TestCordappImpl = copy(targetVersion = targetVersion)
override fun withConfig(config: Map<String, Any>): TestCordappImpl = copy(config = config) override fun withConfig(config: Map<String, Any>): TestCordappImpl = copy(config = config)
override fun signJar(keyStorePath: Path?): TestCordappImpl = copy(signJar = true, keyStorePath = keyStorePath) override fun withOnlyJarContents(): TestCordappImpl = copy(config = emptyMap())
fun withClasses(vararg classes: Class<*>): TestCordappImpl { override val jarFile: Path
return copy(classes = classes.filter { clazz -> packages.none { clazz.name.startsWith("$it.") } }.toSet()) get() {
val jars = TestCordappImpl.findJars(scanPackage)
when (jars.size) {
0 -> throw IllegalArgumentException("Package $scanPackage does not exist")
1 -> return jars.first()
else -> throw IllegalArgumentException("More than one jar found containing package $scanPackage: $jars")
}
}
companion object {
private val packageToRootPaths = ConcurrentHashMap<String, Set<Path>>()
private val projectRootToBuiltJar = ConcurrentHashMap<Path, Path>()
private val log = contextLogger()
fun findJars(scanPackage: String): Set<Path> {
val rootPaths = findRootPaths(scanPackage)
return if (rootPaths.all { it.toString().endsWith(".jar") }) {
// We don't need to do anything more if all the root paths are jars
rootPaths
} else {
// Otherwise we need to build those paths which are local projects and extract the built jar from them
rootPaths.mapTo(HashSet()) { if (it.toString().endsWith(".jar")) it else buildCordappJar(it) }
}
}
private fun findRootPaths(scanPackage: String): Set<Path> {
return packageToRootPaths.computeIfAbsent(scanPackage) {
ClassGraph()
.whitelistPackages(scanPackage)
.scan()
.use { it.allResources }
.asSequence()
.map { it.classpathElementURL.toPath() }
.filterNot { it.toString().endsWith("-tests.jar") }
.map { if (it.toString().endsWith(".jar")) it else findProjectRoot(it) }
.toSet()
}
}
private fun findProjectRoot(path: Path): Path {
var current = path
while (true) {
if ((current / "build.gradle").exists()) {
return current
}
current = current.parent
}
}
private fun buildCordappJar(projectRoot: Path): Path {
return projectRootToBuiltJar.computeIfAbsent(projectRoot) {
val gradlew = findGradlewDir(projectRoot) / (if (SystemUtils.IS_OS_WINDOWS) "gradlew.bat" else "gradlew")
val libs = projectRoot / "build" / "libs"
libs.deleteRecursively()
log.info("Generating CorDapp jar from local project in $projectRoot ...")
val exitCode = ProcessBuilder(gradlew.toString(), "jar").directory(projectRoot.toFile()).inheritIO().start().waitFor()
check(exitCode == 0) { "Unable to generate CorDapp jar from local project in $projectRoot ($exitCode)" }
val jars = libs.list { it.filter { it.toString().endsWith(".jar") }.toList() }
checkNotNull(jars.singleOrNull()) { "Expecting a single built jar in $libs, but instead got $jars" }
}
}
private fun findGradlewDir(path: Path): Path {
var current = path
while (true) {
if ((current / "gradlew").exists() && (current / "gradlew.bat").exists()) {
return current
}
current = current.parent
}
}
} }
} }

View File

@ -0,0 +1,49 @@
package net.corda.testing.node.internal
import com.typesafe.config.ConfigValueFactory
import net.corda.core.internal.copyToDirectory
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.internal.writeText
import net.corda.testing.node.TestCordapp
import java.nio.file.Path
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
/**
* Extends the public [TestCordapp] API with internal extensions for use within the testing framework and for internal testing of the platform.
*
* @property jarFile The jar file this CorDapp represents. Different CorDapps may point to the same file.
*/
interface TestCordappInternal : TestCordapp {
val jarFile: Path
/** Return a copy of this TestCordappInternal but without any metadata, such as configs and signing information. */
fun withOnlyJarContents(): TestCordappInternal
companion object {
fun installCordapps(baseDirectory: Path, nodeSpecificCordapps: Set<TestCordappInternal>, generalCordapps: Set<TestCordappInternal>) {
val nodeSpecificCordappsWithoutMeta = checkNoConflicts(nodeSpecificCordapps)
checkNoConflicts(generalCordapps)
// Precedence is given to node-specific CorDapps
val allCordapps = nodeSpecificCordapps + generalCordapps.filter { it.withOnlyJarContents() !in nodeSpecificCordappsWithoutMeta }
// Ignore any duplicate jar files
val jarToCordapp = allCordapps.associateBy { it.jarFile }
val cordappsDir = baseDirectory / "cordapps"
val configDir = (cordappsDir / "config").createDirectories()
jarToCordapp.forEach { jar, cordapp ->
jar.copyToDirectory(cordappsDir, REPLACE_EXISTING)
val configString = ConfigValueFactory.fromMap(cordapp.config).toConfig().root().render()
(configDir / "${jar.fileName.toString().removeSuffix(".jar")}.conf").writeText(configString)
}
}
private fun checkNoConflicts(cordapps: Set<TestCordappInternal>): Set<TestCordappInternal> {
val cordappsWithoutMeta = cordapps.groupBy { it.withOnlyJarContents() }
cordappsWithoutMeta.forEach { require(it.value.size == 1) { "Conflicting CorDapps specified: ${it.value}" } }
return cordappsWithoutMeta.keys
}
}
}

View File

@ -1,41 +1,52 @@
package net.corda.testing.node.internal package net.corda.testing.node.internal
import io.github.classgraph.ClassGraph
import net.corda.core.internal.cordapp.*
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_NAME
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_VERSION
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_WORKFLOW_NAME
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_WORKFLOW_VERSION
import net.corda.core.internal.cordapp.CordappImpl.Companion.TARGET_PLATFORM_VERSION
import net.corda.core.internal.outputStream
import net.corda.testing.node.TestCordapp import net.corda.testing.node.TestCordapp
import java.io.BufferedOutputStream
import java.nio.file.Path
import java.nio.file.attribute.FileTime
import java.time.Instant
import java.util.jar.Attributes
import java.util.jar.JarFile
import java.util.jar.JarOutputStream
import java.util.jar.Manifest
import java.util.zip.ZipEntry
import kotlin.reflect.KClass import kotlin.reflect.KClass
/**
* Reference to the finance-contracts CorDapp in this repo. The metadata is taken directly from finance/contracts/build.gradle, including the
* fact that the jar is signed. If you need an unsigned jar then use `cordappWithPackages("net.corda.finance.contracts")`.
*
* You will probably need to use [FINANCE_CORDAPPS] instead to get access to the flows as well.
*/
// TODO We can't use net.corda.finance.contracts as finance-workflows contains the package net.corda.finance.contracts.asset.cash.selection. This should be renamed.
@JvmField @JvmField
val FINANCE_CORDAPP: TestCordappImpl = cordappForPackages("net.corda.finance") val FINANCE_CONTRACTS_CORDAPP: TestCordappImpl = findCordapp("net.corda.finance.schemas")
/** Creates a [TestCordappImpl] for each package. */ /**
fun cordappsForPackages(vararg packageNames: String): List<TestCordappImpl> = cordappsForPackages(packageNames.asList()) * Reference to the finance-workflows CorDapp in this repo. The metadata is taken directly from finance/workflows/build.gradle, including the
* fact that the jar is signed. If you need an unsigned jar then use `cordappWithPackages("net.corda.finance.flows")`.
*
* You will probably need to use [FINANCE_CORDAPPS] instead to get access to the contract classes as well.
*/
@JvmField
val FINANCE_WORKFLOWS_CORDAPP: TestCordappImpl = findCordapp("net.corda.finance.flows")
fun cordappsForPackages(packageNames: Iterable<String>): List<TestCordappImpl> { @JvmField
return simplifyScanPackages(packageNames).map { cordappForPackages(it) } val FINANCE_CORDAPPS: List<TestCordappInternal> = listOf(FINANCE_CONTRACTS_CORDAPP, FINANCE_WORKFLOWS_CORDAPP)
fun cordappsForPackages(vararg packageNames: String): Set<CustomCordapp> = cordappsForPackages(packageNames.asList())
fun cordappsForPackages(packageNames: Iterable<String>): Set<CustomCordapp> {
return simplifyScanPackages(packageNames).mapTo(HashSet()) { cordappWithPackages(it) }
} }
/** Creates a single [TestCordappImpl] containing all the given packges. */ /**
fun cordappForPackages(vararg packageNames: String): TestCordappImpl { * Create a *custom* CorDapp which contains all the classes and resoures located in the given packages. The CorDapp's metadata will be the
return TestCordapp.Factory.fromPackages(*packageNames) as TestCordappImpl * default values as defined in the [CustomCordapp] c'tor. Use the `copy` to change them. This means the metadata will *not* be the one defined
} * in the original CorDapp(s) that the given packages may represent. If this is not what you want then use [findCordapp] instead.
*/
fun cordappWithPackages(vararg packageNames: String): CustomCordapp = CustomCordapp(packages = simplifyScanPackages(packageNames.asList()))
fun cordappForClasses(vararg classes: Class<*>): TestCordappImpl = cordappForPackages().withClasses(*classes) /** Create a *custom* CorDapp which contains just the given classes. */
// TODO Rename to cordappWithClasses
fun cordappForClasses(vararg classes: Class<*>): CustomCordapp = CustomCordapp(packages = emptySet(), classes = classes.toSet())
/**
* Find the single CorDapp jar on the current classpath which contains the given package. This is a convenience method for
* [TestCordapp.Factory.findCordapp] but returns the internal [TestCordappImpl].
*/
fun findCordapp(scanPackage: String): TestCordappImpl = TestCordapp.Factory.findCordapp(scanPackage) as TestCordappImpl
fun getCallerClass(directCallerClass: KClass<*>): Class<*>? { fun getCallerClass(directCallerClass: KClass<*>): Class<*>? {
val stackTrace = Throwable().stackTrace val stackTrace = Throwable().stackTrace
@ -62,52 +73,3 @@ fun simplifyScanPackages(scanPackages: Iterable<String>): Set<String> {
} }
} }
} }
fun TestCordappImpl.packageAsJar(file: Path) {
// Don't mention "classes" in the error message as that feature is only available internally
require(packages.isNotEmpty() || classes.isNotEmpty()) { "At least one package must be specified" }
val scanResult = ClassGraph()
.whitelistPackages(*packages.toTypedArray())
.whitelistClasses(*classes.map { it.name }.toTypedArray())
.scan()
scanResult.use {
val manifest = createTestManifest(name, title, version, vendor, targetVersion)
JarOutputStream(file.outputStream()).use { jos ->
val time = FileTime.from(Instant.EPOCH)
val manifestEntry = ZipEntry(JarFile.MANIFEST_NAME).setCreationTime(time).setLastAccessTime(time).setLastModifiedTime(time)
jos.putNextEntry(manifestEntry)
manifest.write(BufferedOutputStream(jos))
jos.closeEntry()
// The same resource may be found in different locations (this will happen when running from gradle) so just
// pick the first one found.
scanResult.allResources.asMap().forEach { path, resourceList ->
val entry = ZipEntry(path).setCreationTime(time).setLastAccessTime(time).setLastModifiedTime(time)
jos.putNextEntry(entry)
resourceList[0].open().use { it.copyTo(jos) }
jos.closeEntry()
}
}
}
}
fun createTestManifest(name: String, title: String, version: String, vendor: String, targetVersion: Int): Manifest {
val manifest = Manifest()
// Mandatory manifest attribute. If not present, all other entries are silently skipped.
manifest[Attributes.Name.MANIFEST_VERSION.toString()] = "1.0"
manifest["Name"] = name
manifest[Attributes.Name.IMPLEMENTATION_TITLE] = title
manifest[Attributes.Name.IMPLEMENTATION_VERSION] = version
manifest[Attributes.Name.IMPLEMENTATION_VENDOR] = vendor
manifest[CORDAPP_CONTRACT_NAME] = name
manifest[CORDAPP_CONTRACT_VERSION] = version
manifest[CORDAPP_WORKFLOW_NAME] = name
manifest[CORDAPP_WORKFLOW_VERSION] = version
manifest[TARGET_PLATFORM_VERSION] = targetVersion.toString()
return manifest
}

View File

@ -0,0 +1,80 @@
package net.corda.testing.node.internal
import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.internal.cordapp.get
import net.corda.core.internal.inputStream
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.nio.file.Path
import java.util.jar.JarInputStream
class CustomCordappTest {
@Rule
@JvmField
val tempFolder = TemporaryFolder()
@Test
fun `packageAsJar writes out the CorDapp info into the manifest`() {
val cordapp = cordappWithPackages("net.corda.testing.node.internal").copy(targetPlatformVersion = 123, name = "TestCordappsUtilsTest")
val jarFile = packageAsJar(cordapp)
JarInputStream(jarFile.inputStream()).use {
assertThat(it.manifest[CordappImpl.TARGET_PLATFORM_VERSION]).isEqualTo("123")
assertThat(it.manifest[CordappImpl.CORDAPP_CONTRACT_NAME]).isEqualTo("TestCordappsUtilsTest")
assertThat(it.manifest[CordappImpl.CORDAPP_WORKFLOW_NAME]).isEqualTo("TestCordappsUtilsTest")
}
}
@Test
fun `packageAsJar on leaf package`() {
val entries = packageAsJarThenReadBack(cordappWithPackages("net.corda.testing.node.internal"))
assertThat(entries).contains(
"net/corda/testing/node/internal/TestCordappsUtilsTest.class",
"net/corda/testing/node/internal/resource.txt" // Make sure non-class resource files are also picked up
).doesNotContain(
"net/corda/testing/node/MockNetworkTest.class"
)
// Make sure the MockNetworkTest class does actually exist to ensure the above is not a false-positive
assertThat(javaClass.classLoader.getResource("net/corda/testing/node/MockNetworkTest.class")).isNotNull()
}
@Test
fun `packageAsJar on package with sub-packages`() {
val entries = packageAsJarThenReadBack(cordappWithPackages("net.corda.testing.node"))
assertThat(entries).contains(
"net/corda/testing/node/internal/TestCordappsUtilsTest.class",
"net/corda/testing/node/internal/resource.txt",
"net/corda/testing/node/MockNetworkTest.class"
)
}
@Test
fun `packageAsJar on single class`() {
val entries = packageAsJarThenReadBack(cordappForClasses(InternalMockNetwork::class.java))
assertThat(entries).containsOnly("${InternalMockNetwork::class.java.name.replace('.', '/')}.class")
}
private fun packageAsJar(cordapp: CustomCordapp): Path {
val jarFile = tempFolder.newFile().toPath()
cordapp.packageAsJar(jarFile)
return jarFile
}
private fun packageAsJarThenReadBack(cordapp: CustomCordapp): List<String> {
val jarFile = packageAsJar(cordapp)
val entries = ArrayList<String>()
JarInputStream(jarFile.inputStream()).use {
while (true) {
val e = it.nextJarEntry ?: break
entries += e.name
it.closeEntry()
}
}
return entries
}
}

View File

@ -1,22 +1,9 @@
package net.corda.testing.node.internal package net.corda.testing.node.internal
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_NAME
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_WORKFLOW_NAME
import net.corda.core.internal.cordapp.CordappImpl.Companion.TARGET_PLATFORM_VERSION
import net.corda.core.internal.cordapp.get
import net.corda.core.internal.inputStream
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.nio.file.Path
import java.util.jar.JarInputStream
class TestCordappsUtilsTest { class TestCordappsUtilsTest {
@Rule
@JvmField
val tempFolder = TemporaryFolder()
@Test @Test
fun `test simplifyScanPackages`() { fun `test simplifyScanPackages`() {
assertThat(simplifyScanPackages(emptyList())).isEmpty() assertThat(simplifyScanPackages(emptyList())).isEmpty()
@ -28,70 +15,4 @@ class TestCordappsUtilsTest {
assertThat(simplifyScanPackages(listOf("com.foobar", "com.foo.bar"))).containsExactlyInAnyOrder("com.foobar", "com.foo.bar") assertThat(simplifyScanPackages(listOf("com.foobar", "com.foo.bar"))).containsExactlyInAnyOrder("com.foobar", "com.foo.bar")
assertThat(simplifyScanPackages(listOf("com.foobar", "com.foo"))).containsExactlyInAnyOrder("com.foobar", "com.foo") assertThat(simplifyScanPackages(listOf("com.foobar", "com.foo"))).containsExactlyInAnyOrder("com.foobar", "com.foo")
} }
@Test
fun `packageAsJar writes out the CorDapp info into the manifest`() {
val cordapp = cordappForPackages("net.corda.testing.node.internal")
.withTargetVersion(123)
.withName("TestCordappsUtilsTest")
val jarFile = packageAsJar(cordapp)
JarInputStream(jarFile.inputStream()).use {
assertThat(it.manifest[TARGET_PLATFORM_VERSION]).isEqualTo("123")
assertThat(it.manifest[CORDAPP_CONTRACT_NAME]).isEqualTo("TestCordappsUtilsTest")
assertThat(it.manifest[CORDAPP_WORKFLOW_NAME]).isEqualTo("TestCordappsUtilsTest")
}
}
@Test
fun `packageAsJar on leaf package`() {
val entries = packageAsJarThenReadBack(cordappForPackages("net.corda.testing.node.internal"))
assertThat(entries).contains(
"net/corda/testing/node/internal/TestCordappsUtilsTest.class",
"net/corda/testing/node/internal/resource.txt" // Make sure non-class resource files are also picked up
).doesNotContain(
"net/corda/testing/node/MockNetworkTest.class"
)
// Make sure the MockNetworkTest class does actually exist to ensure the above is not a false-positive
assertThat(javaClass.classLoader.getResource("net/corda/testing/node/MockNetworkTest.class")).isNotNull()
}
@Test
fun `packageAsJar on package with sub-packages`() {
val entries = packageAsJarThenReadBack(cordappForPackages("net.corda.testing.node"))
assertThat(entries).contains(
"net/corda/testing/node/internal/TestCordappsUtilsTest.class",
"net/corda/testing/node/internal/resource.txt",
"net/corda/testing/node/MockNetworkTest.class"
)
}
@Test
fun `packageAsJar on single class`() {
val entries = packageAsJarThenReadBack(cordappForClasses(InternalMockNetwork::class.java))
assertThat(entries).containsOnly("${InternalMockNetwork::class.java.name.replace('.', '/')}.class")
}
private fun packageAsJar(cordapp: TestCordappImpl): Path {
val jarFile = tempFolder.newFile().toPath()
cordapp.packageAsJar(jarFile)
return jarFile
}
private fun packageAsJarThenReadBack(cordapp: TestCordappImpl): List<String> {
val jarFile = packageAsJar(cordapp)
val entries = ArrayList<String>()
JarInputStream(jarFile.inputStream()).use {
while (true) {
val e = it.nextJarEntry ?: break
entries += e.name
it.closeEntry()
}
}
return entries
}
} }

View File

@ -31,7 +31,8 @@ import net.corda.testing.core.BOB_NAME
import net.corda.testing.driver.* import net.corda.testing.driver.*
import net.corda.testing.driver.internal.incrementalPortAllocation import net.corda.testing.driver.internal.incrementalPortAllocation
import net.corda.testing.node.User import net.corda.testing.node.User
import net.corda.testing.node.internal.FINANCE_CORDAPP import net.corda.testing.node.internal.FINANCE_CORDAPPS
import net.corda.testing.node.internal.FINANCE_WORKFLOWS_CORDAPP
import java.time.Instant import java.time.Instant
import java.util.* import java.util.*
@ -71,7 +72,7 @@ class ExplorerSimulation(private val options: OptionSet) {
val portAllocation = incrementalPortAllocation(20000) val portAllocation = incrementalPortAllocation(20000)
driver(DriverParameters( driver(DriverParameters(
portAllocation = portAllocation, portAllocation = portAllocation,
cordappsForAllNodes = listOf(FINANCE_CORDAPP), cordappsForAllNodes = FINANCE_CORDAPPS,
waitForAllNodesToFinish = true, waitForAllNodesToFinish = true,
jmxPolicy = JmxPolicy.defaultEnabled() jmxPolicy = JmxPolicy.defaultEnabled()
)) { )) {
@ -83,12 +84,12 @@ class ExplorerSimulation(private val options: OptionSet) {
val issuerGBP = startNode(NodeParameters( val issuerGBP = startNode(NodeParameters(
providedName = ukBankName, providedName = ukBankName,
rpcUsers = listOf(manager), rpcUsers = listOf(manager),
additionalCordapps = listOf(FINANCE_CORDAPP.withConfig(mapOf("issuableCurrencies" to listOf("GBP")))) additionalCordapps = listOf(FINANCE_WORKFLOWS_CORDAPP.copy(config = mapOf("issuableCurrencies" to listOf("GBP"))))
)) ))
val issuerUSD = startNode(NodeParameters( val issuerUSD = startNode(NodeParameters(
providedName = usaBankName, providedName = usaBankName,
rpcUsers = listOf(manager), rpcUsers = listOf(manager),
additionalCordapps = listOf(FINANCE_CORDAPP.withConfig(mapOf("issuableCurrencies" to listOf("USD")))) additionalCordapps = listOf(FINANCE_WORKFLOWS_CORDAPP.copy(config = mapOf("issuableCurrencies" to listOf("USD"))))
)) ))
notaryNode = defaultNotaryNode.get() notaryNode = defaultNotaryNode.get()