mirror of
https://github.com/corda/corda.git
synced 2025-02-21 09:51:57 +00:00
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:
parent
4aaefb4fe9
commit
830959c9f7
5
.idea/compiler.xml
generated
5
.idea/compiler.xml
generated
@ -39,6 +39,8 @@
|
||||
<module name="contracts-states_integrationTest" target="1.8" />
|
||||
<module name="contracts-states_main" 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_smokeTest" 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_main" 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>
|
||||
</component>
|
||||
<component name="JavacSettings">
|
||||
|
@ -18,7 +18,7 @@ import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.core.CHARLIE_NAME
|
||||
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.startFlow
|
||||
import org.junit.After
|
||||
@ -35,7 +35,7 @@ class IdentitySyncFlowTests {
|
||||
fun before() {
|
||||
// We run this in parallel threads to help catch any race conditions that may exist.
|
||||
mockNet = InternalMockNetwork(
|
||||
cordappsForAllNodes = listOf(FINANCE_CORDAPP),
|
||||
cordappsForAllNodes = FINANCE_CORDAPPS,
|
||||
networkSendManuallyPumped = false,
|
||||
threadPerNode = true
|
||||
)
|
||||
|
@ -387,7 +387,7 @@ val Class<*>.location: URL get() = protectionDomain.codeSource.location
|
||||
|
||||
/** Convenience method to get the package name of a class literal. */
|
||||
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)
|
||||
|
||||
|
@ -14,7 +14,6 @@ import net.corda.finance.issuedBy
|
||||
import net.corda.testing.core.*
|
||||
import net.corda.testing.internal.matchers.flow.willReturn
|
||||
import net.corda.testing.internal.matchers.flow.willThrow
|
||||
import net.corda.testing.node.TestCordapp
|
||||
import net.corda.testing.node.internal.*
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
||||
@ -77,7 +76,7 @@ class FinalityFlowTests : WithFinality {
|
||||
@Test
|
||||
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
|
||||
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 resultFuture = CordappResolver.withCordapp(targetPlatformVersion = 3) {
|
||||
@Suppress("DEPRECATION")
|
||||
@ -87,7 +86,7 @@ class FinalityFlowTests : WithFinality {
|
||||
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))
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,15 @@ Unreleased
|
||||
This allows Corda 4 signed CorDapps using signature constraints to consume existing hash constrained states generated
|
||||
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 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
|
||||
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.
|
||||
|
||||
* Removed buggy :serverNameTablePrefix: configuration.
|
||||
|
@ -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.BOB_NAME;
|
||||
import static net.corda.testing.driver.Driver.driver;
|
||||
import static net.corda.testing.node.internal.TestCordappsUtilsKt.FINANCE_CORDAPPS;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class JavaIntegrationTestingTutorial {
|
||||
@ -40,7 +41,7 @@ public class JavaIntegrationTestingTutorial {
|
||||
// START 1
|
||||
driver(new DriverParameters()
|
||||
.withStartNodesInProcess(true)
|
||||
.withExtraCordappPackagesToScan(singletonList("net.corda.finance")), dsl -> {
|
||||
.withCordappsForAllNodes(FINANCE_CORDAPPS), dsl -> {
|
||||
|
||||
User aliceUser = new User("aliceUser", "testPassword1", new HashSet<>(asList(
|
||||
startFlow(CashIssueAndPaymentFlow.class),
|
||||
|
@ -20,6 +20,7 @@ import net.corda.testing.core.*
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.node.User
|
||||
import net.corda.testing.node.internal.FINANCE_CORDAPPS
|
||||
import org.junit.Test
|
||||
import rx.Observable
|
||||
import java.util.*
|
||||
@ -29,7 +30,7 @@ class KotlinIntegrationTestingTutorial {
|
||||
@Test
|
||||
fun `alice bob cash exchange example`() {
|
||||
// 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(
|
||||
startFlow<CashIssueAndPaymentFlow>(),
|
||||
invokeRpc("vaultTrackBy")
|
||||
|
@ -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
|
||||
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.
|
||||
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"))
|
||||
)
|
||||
|
||||
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
|
||||
-------------------------------------------------------------
|
||||
|
||||
|
@ -7,7 +7,7 @@ import net.corda.finance.USD
|
||||
import net.corda.testing.node.MockNetwork
|
||||
import net.corda.testing.node.MockNetworkParameters
|
||||
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.junit.After
|
||||
import org.junit.Test
|
||||
@ -21,7 +21,7 @@ class CashConfigDataFlowTest {
|
||||
@Test
|
||||
fun `issuable currencies read in from cordapp config`() {
|
||||
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()
|
||||
assertThat(config.issuableCurrencies).containsExactly(EUR, USD)
|
||||
|
@ -26,8 +26,7 @@ import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import net.corda.testing.core.TestIdentity
|
||||
import net.corda.testing.internal.MockCordappConfigProvider
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.node.internal.TestCordappDirectories
|
||||
import net.corda.testing.node.internal.cordappsForPackages
|
||||
import net.corda.testing.node.internal.cordappWithPackages
|
||||
import net.corda.testing.services.MockAttachmentStorage
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Assert.assertEquals
|
||||
@ -112,7 +111,6 @@ class AttachmentsClassLoaderStaticContractTests {
|
||||
}
|
||||
|
||||
private fun cordappLoaderForPackages(packages: Collection<String>): CordappLoader {
|
||||
val dirs = cordappsForPackages(packages).map { TestCordappDirectories.getJarDirectory(it) }
|
||||
return JarScanningCordappLoader.fromDirectories(dirs)
|
||||
return JarScanningCordappLoader.fromJarUrls(listOf(cordappWithPackages(*packages.toTypedArray()).jarFile.toUri().toURL()))
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ import net.corda.core.contracts.*
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
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.messaging.startFlow
|
||||
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.internal.incrementalPortAllocation
|
||||
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 org.junit.Assume.assumeFalse
|
||||
import org.junit.Test
|
||||
@ -34,9 +36,9 @@ import kotlin.test.assertNotNull
|
||||
|
||||
class SignatureConstraintVersioningTests {
|
||||
|
||||
private val base = cordappForPackages(MessageState::class.packageName, DummyMessageContract::class.packageName)
|
||||
private val oldCordapp = base.withVersion("2")
|
||||
private val newCordapp = base.withVersion("3")
|
||||
private val base = cordappWithPackages(MessageState::class.packageName, DummyMessageContract::class.packageName).signed()
|
||||
private val oldCordapp = base.copy(versionId = 2)
|
||||
private val newCordapp = base.copy(versionId = 3)
|
||||
private val user = User("mark", "dadada", setOf(startFlow<CreateMessage>(), startFlow<ConsumeMessage>(), invokeRpc("vaultQuery")))
|
||||
private val message = Message("Hello world!")
|
||||
private val transformetMessage = Message(message.value + "A")
|
||||
@ -45,11 +47,13 @@ class SignatureConstraintVersioningTests {
|
||||
fun `can evolve from lower contract class version to higher one`() {
|
||||
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(),
|
||||
networkParameters = testNetworkParameters(notaries = emptyList(), minimumPlatformVersion = 4), signCordapps = true) {
|
||||
var nodeName = {
|
||||
val nodeHandle = startNode(NodeParameters(rpcUsers = listOf(user), additionalCordapps = listOf(oldCordapp), regenerateCordappsOnStart = true)).getOrThrow()
|
||||
networkParameters = testNetworkParameters(notaries = emptyList(), minimumPlatformVersion = 4)
|
||||
) {
|
||||
val nodeName = {
|
||||
val nodeHandle = startNode(NodeParameters(rpcUsers = listOf(user), additionalCordapps = listOf(oldCordapp))).getOrThrow()
|
||||
val nodeName = nodeHandle.nodeInfo.singleIdentity().name
|
||||
CordaRPCClient(nodeHandle.rpcAddress).start(user.username, user.password).use {
|
||||
it.proxy.startFlow(::CreateMessage, message, defaultNotaryIdentity).returnValue.getOrThrow()
|
||||
@ -57,8 +61,9 @@ class SignatureConstraintVersioningTests {
|
||||
nodeHandle.stop()
|
||||
nodeName
|
||||
}()
|
||||
var result = {
|
||||
val nodeHandle = startNode(NodeParameters(providedName = nodeName, rpcUsers = listOf(user), additionalCordapps = listOf(newCordapp), regenerateCordappsOnStart = true)).getOrThrow()
|
||||
val result = {
|
||||
(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 {
|
||||
val page = it.proxy.vaultQuery(MessageState::class.java)
|
||||
page.states.singleOrNull()
|
||||
@ -87,9 +92,9 @@ class SignatureConstraintVersioningTests {
|
||||
|
||||
val stateAndRef: StateAndRef<MessageState>? = internalDriver(inMemoryDB = false,
|
||||
startNodesInProcess = isQuasarAgentSpecified(),
|
||||
networkParameters = testNetworkParameters(notaries = emptyList(), minimumPlatformVersion = 4), signCordapps = true) {
|
||||
var nodeName = {
|
||||
val nodeHandle = startNode(NodeParameters(rpcUsers = listOf(user), additionalCordapps = listOf(newCordapp), regenerateCordappsOnStart = true),
|
||||
networkParameters = testNetworkParameters(notaries = emptyList(), minimumPlatformVersion = 4)) {
|
||||
val nodeName = {
|
||||
val nodeHandle = startNode(NodeParameters(rpcUsers = listOf(user), additionalCordapps = listOf(newCordapp)),
|
||||
customOverrides = mapOf("h2Settings.address" to "localhost:$port")).getOrThrow()
|
||||
val nodeName = nodeHandle.nodeInfo.singleIdentity().name
|
||||
CordaRPCClient(nodeHandle.rpcAddress).start(user.username, user.password).use {
|
||||
@ -98,8 +103,9 @@ class SignatureConstraintVersioningTests {
|
||||
nodeHandle.stop()
|
||||
nodeName
|
||||
}()
|
||||
var result = {
|
||||
val nodeHandle = startNode(NodeParameters(providedName = nodeName, rpcUsers = listOf(user), additionalCordapps = listOf(oldCordapp), regenerateCordappsOnStart = true),
|
||||
val result = {
|
||||
(baseDirectory(nodeName) / "cordapps").deleteRecursively()
|
||||
val nodeHandle = startNode(NodeParameters(providedName = nodeName, rpcUsers = listOf(user), additionalCordapps = listOf(oldCordapp)),
|
||||
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
|
||||
|
@ -4,6 +4,8 @@ import net.corda.core.contracts.HashAttachmentConstraint
|
||||
import net.corda.core.contracts.SignatureAttachmentConstraint
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
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.startFlow
|
||||
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.node.NotarySpec
|
||||
import net.corda.testing.node.User
|
||||
import net.corda.testing.node.internal.FINANCE_CORDAPP
|
||||
import org.assertj.core.api.Assertions
|
||||
import net.corda.testing.node.internal.cordappWithPackages
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Test
|
||||
|
||||
class CordappConstraintsTests {
|
||||
@ -35,6 +37,8 @@ class CordappConstraintsTests {
|
||||
invokeRpc(CordaRPCOps::wellKnownPartyFromX500Name),
|
||||
invokeRpc(CordaRPCOps::notaryIdentities),
|
||||
invokeRpc("vaultTrackByCriteria")))
|
||||
val UNSIGNED_FINANCE_CORDAPP = cordappWithPackages("net.corda.finance")
|
||||
val SIGNED_FINANCE_CORDAPP = UNSIGNED_FINANCE_CORDAPP.signed()
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -43,10 +47,11 @@ class CordappConstraintsTests {
|
||||
networkParameters = testNetworkParameters(minimumPlatformVersion = 4),
|
||||
inMemoryDB = false
|
||||
)) {
|
||||
|
||||
val alice = startNode(NodeParameters(additionalCordapps = listOf(FINANCE_CORDAPP.signJar()),
|
||||
val alice = startNode(NodeParameters(
|
||||
additionalCordapps = listOf(SIGNED_FINANCE_CORDAPP),
|
||||
providedName = ALICE_NAME,
|
||||
rpcUsers = listOf(user))).getOrThrow()
|
||||
rpcUsers = listOf(user)
|
||||
)).getOrThrow()
|
||||
|
||||
val expected = 500.DOLLARS
|
||||
val ref = OpaqueBytes.of(0x01)
|
||||
@ -57,8 +62,8 @@ class CordappConstraintsTests {
|
||||
val states = alice.rpc.vaultQueryBy<Cash.State>().states
|
||||
printVault(alice, states)
|
||||
|
||||
Assertions.assertThat(states).hasSize(1)
|
||||
Assertions.assertThat(states[0].state.constraint is SignatureAttachmentConstraint)
|
||||
assertThat(states).hasSize(1)
|
||||
assertThat(states[0].state.constraint).isInstanceOf(SignatureAttachmentConstraint::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,11 +73,12 @@ class CordappConstraintsTests {
|
||||
networkParameters = testNetworkParameters(minimumPlatformVersion = 4),
|
||||
inMemoryDB = false
|
||||
)) {
|
||||
|
||||
println("Starting the node using unsigned contract jar ...")
|
||||
val alice = startNode(NodeParameters(providedName = ALICE_NAME,
|
||||
additionalCordapps = listOf(FINANCE_CORDAPP),
|
||||
rpcUsers = listOf(user))).getOrThrow()
|
||||
val alice = startNode(NodeParameters(
|
||||
providedName = ALICE_NAME,
|
||||
additionalCordapps = listOf(UNSIGNED_FINANCE_CORDAPP),
|
||||
rpcUsers = listOf(user)
|
||||
)).getOrThrow()
|
||||
|
||||
val expected = 500.DOLLARS
|
||||
val ref = OpaqueBytes.of(0x01)
|
||||
@ -82,7 +88,7 @@ class CordappConstraintsTests {
|
||||
// Query vault
|
||||
val states = alice.rpc.vaultQueryBy<Cash.State>().states
|
||||
printVault(alice, states)
|
||||
Assertions.assertThat(states).hasSize(1)
|
||||
assertThat(states).hasSize(1)
|
||||
|
||||
// Restart the node and re-query the vault
|
||||
println("Shutting down the node ...")
|
||||
@ -90,14 +96,15 @@ class CordappConstraintsTests {
|
||||
alice.stop()
|
||||
|
||||
println("Restarting the node using signed contract jar ...")
|
||||
val restartedNode = startNode(NodeParameters(providedName = ALICE_NAME,
|
||||
additionalCordapps = listOf(FINANCE_CORDAPP.signJar()),
|
||||
regenerateCordappsOnStart = true
|
||||
(baseDirectory(ALICE_NAME) / "cordapps").deleteRecursively()
|
||||
val restartedNode = startNode(NodeParameters(
|
||||
providedName = ALICE_NAME,
|
||||
additionalCordapps = listOf(SIGNED_FINANCE_CORDAPP)
|
||||
)).getOrThrow()
|
||||
|
||||
val statesAfterRestart = restartedNode.rpc.vaultQueryBy<Cash.State>().states
|
||||
printVault(restartedNode, statesAfterRestart)
|
||||
Assertions.assertThat(statesAfterRestart).hasSize(1)
|
||||
assertThat(statesAfterRestart).hasSize(1)
|
||||
|
||||
val issueTx2 = restartedNode.rpc.startFlow(::CashIssueFlow, expected, ref, defaultNotaryIdentity).returnValue.getOrThrow()
|
||||
println("Issued 2nd transaction: $issueTx2")
|
||||
@ -106,19 +113,19 @@ class CordappConstraintsTests {
|
||||
val allStates = restartedNode.rpc.vaultQueryBy<Cash.State>().states
|
||||
printVault(restartedNode, allStates)
|
||||
|
||||
Assertions.assertThat(allStates).hasSize(2)
|
||||
Assertions.assertThat(allStates[0].state.constraint is HashAttachmentConstraint)
|
||||
Assertions.assertThat(allStates[1].state.constraint is SignatureAttachmentConstraint)
|
||||
assertThat(allStates).hasSize(2)
|
||||
assertThat(allStates[0].state.constraint).isInstanceOf(HashAttachmentConstraint::class.java)
|
||||
assertThat(allStates[1].state.constraint).isInstanceOf(SignatureAttachmentConstraint::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `issue and consume cash using hash constraints`() {
|
||||
driver(DriverParameters(cordappsForAllNodes = listOf(FINANCE_CORDAPP),
|
||||
extraCordappPackagesToScan = listOf("net.corda.finance"),
|
||||
driver(DriverParameters(
|
||||
cordappsForAllNodes = listOf(UNSIGNED_FINANCE_CORDAPP),
|
||||
networkParameters = testNetworkParameters(minimumPlatformVersion = 4),
|
||||
inMemoryDB = false)) {
|
||||
|
||||
inMemoryDB = false
|
||||
)) {
|
||||
val (alice, bob) = listOf(
|
||||
startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)),
|
||||
startNode(providedName = BOB_NAME, rpcUsers = listOf(user))
|
||||
@ -145,10 +152,10 @@ class CordappConstraintsTests {
|
||||
val aliceStates = aliceQuery.states
|
||||
printVault(alice, aliceQuery.states)
|
||||
|
||||
Assertions.assertThat(aliceStates).hasSize(1)
|
||||
Assertions.assertThat(aliceStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS)
|
||||
Assertions.assertThat(aliceQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.CONSUMED)
|
||||
Assertions.assertThat(aliceQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.HASH)
|
||||
assertThat(aliceStates).hasSize(1)
|
||||
assertThat(aliceStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS)
|
||||
assertThat(aliceQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.CONSUMED)
|
||||
assertThat(aliceQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.HASH)
|
||||
|
||||
// Check Bob Vault Updates
|
||||
vaultUpdatesBob.expectEvents {
|
||||
@ -165,23 +172,23 @@ class CordappConstraintsTests {
|
||||
val bobStates = bobQuery.states
|
||||
printVault(bob, bobQuery.states)
|
||||
|
||||
Assertions.assertThat(bobStates).hasSize(1)
|
||||
Assertions.assertThat(bobStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS)
|
||||
Assertions.assertThat(bobQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.UNCONSUMED)
|
||||
Assertions.assertThat(bobQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.HASH)
|
||||
assertThat(bobStates).hasSize(1)
|
||||
assertThat(bobStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS)
|
||||
assertThat(bobQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.UNCONSUMED)
|
||||
assertThat(bobQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.HASH)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `issue and consume cash using signature constraints`() {
|
||||
driver(DriverParameters(cordappsForAllNodes = listOf(FINANCE_CORDAPP.signJar()),
|
||||
extraCordappPackagesToScan = listOf("net.corda.finance"),
|
||||
driver(DriverParameters(
|
||||
cordappsForAllNodes = listOf(SIGNED_FINANCE_CORDAPP),
|
||||
networkParameters = testNetworkParameters(minimumPlatformVersion = 4),
|
||||
inMemoryDB = false)) {
|
||||
|
||||
inMemoryDB = false
|
||||
)) {
|
||||
val (alice, bob) = listOf(
|
||||
startNode(NodeParameters(providedName = ALICE_NAME, rpcUsers = listOf(user), additionalCordapps = listOf(FINANCE_CORDAPP.signJar()))),
|
||||
startNode(NodeParameters(providedName = BOB_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)))
|
||||
).map { it.getOrThrow() }
|
||||
|
||||
// Issue Cash
|
||||
@ -205,10 +212,10 @@ class CordappConstraintsTests {
|
||||
val aliceStates = aliceQuery.states
|
||||
printVault(alice, aliceStates)
|
||||
|
||||
Assertions.assertThat(aliceStates).hasSize(1)
|
||||
Assertions.assertThat(aliceStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS)
|
||||
Assertions.assertThat(aliceQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.CONSUMED)
|
||||
Assertions.assertThat(aliceQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.SIGNATURE)
|
||||
assertThat(aliceStates).hasSize(1)
|
||||
assertThat(aliceStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS)
|
||||
assertThat(aliceQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.CONSUMED)
|
||||
assertThat(aliceQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.SIGNATURE)
|
||||
|
||||
// Check Bob Vault Updates
|
||||
vaultUpdatesBob.expectEvents {
|
||||
@ -225,27 +232,28 @@ class CordappConstraintsTests {
|
||||
val bobStates = bobQuery.states
|
||||
printVault(bob, bobStates)
|
||||
|
||||
Assertions.assertThat(bobStates).hasSize(1)
|
||||
Assertions.assertThat(bobStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS)
|
||||
Assertions.assertThat(bobQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.UNCONSUMED)
|
||||
Assertions.assertThat(bobQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.SIGNATURE)
|
||||
assertThat(bobStates).hasSize(1)
|
||||
assertThat(bobStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS)
|
||||
assertThat(bobQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.UNCONSUMED)
|
||||
assertThat(bobQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.SIGNATURE)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `issue cash and transfer using hash to signature constraints migration`() {
|
||||
|
||||
// signing key setup
|
||||
val keyStoreDir = SelfCleaningDir()
|
||||
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)),
|
||||
extraCordappPackagesToScan = listOf("net.corda.finance"),
|
||||
networkParameters = testNetworkParameters(minimumPlatformVersion = 4,
|
||||
packageOwnership = mapOf("net.corda.finance.contracts.asset" to packageOwnerKey)),
|
||||
inMemoryDB = false)) {
|
||||
|
||||
networkParameters = testNetworkParameters(
|
||||
minimumPlatformVersion = 4,
|
||||
packageOwnership = mapOf("net.corda.finance.contracts.asset" to packageOwnerKey)
|
||||
),
|
||||
inMemoryDB = false
|
||||
)) {
|
||||
val (alice, bob) = listOf(
|
||||
startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)),
|
||||
startNode(providedName = BOB_NAME, rpcUsers = listOf(user))
|
||||
@ -270,15 +278,17 @@ class CordappConstraintsTests {
|
||||
bob.stop()
|
||||
|
||||
println("Restarting the node for $ALICE_NAME ...")
|
||||
val restartedAlice = startNode(NodeParameters(providedName = ALICE_NAME,
|
||||
additionalCordapps = listOf(FINANCE_CORDAPP.signJar(keyStoreDir.path)),
|
||||
regenerateCordappsOnStart = true
|
||||
(baseDirectory(ALICE_NAME) / "cordapps").deleteRecursively()
|
||||
val restartedAlice = startNode(NodeParameters(
|
||||
providedName = ALICE_NAME,
|
||||
additionalCordapps = listOf(UNSIGNED_FINANCE_CORDAPP.signed(keyStoreDir.path))
|
||||
)).getOrThrow()
|
||||
|
||||
println("Restarting the node for $BOB_NAME ...")
|
||||
val restartedBob = startNode(NodeParameters(providedName = BOB_NAME,
|
||||
additionalCordapps = listOf(FINANCE_CORDAPP.signJar(keyStoreDir.path)),
|
||||
regenerateCordappsOnStart = true
|
||||
(baseDirectory(BOB_NAME) / "cordapps").deleteRecursively()
|
||||
val restartedBob = startNode(NodeParameters(
|
||||
providedName = BOB_NAME,
|
||||
additionalCordapps = listOf(UNSIGNED_FINANCE_CORDAPP.signed(keyStoreDir.path))
|
||||
)).getOrThrow()
|
||||
|
||||
// Register for Bob vault updates
|
||||
@ -295,10 +305,10 @@ class CordappConstraintsTests {
|
||||
val aliceStates = aliceQuery.states
|
||||
printVault(alice, aliceStates)
|
||||
|
||||
Assertions.assertThat(aliceStates).hasSize(1)
|
||||
Assertions.assertThat(aliceStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS)
|
||||
Assertions.assertThat(aliceQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.CONSUMED)
|
||||
Assertions.assertThat(aliceQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.HASH)
|
||||
assertThat(aliceStates).hasSize(1)
|
||||
assertThat(aliceStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS)
|
||||
assertThat(aliceQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.CONSUMED)
|
||||
assertThat(aliceQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.HASH)
|
||||
|
||||
// Check Bob Vault Updates
|
||||
vaultUpdatesBob.expectEvents {
|
||||
@ -316,10 +326,10 @@ class CordappConstraintsTests {
|
||||
val bobStates = bobQuery.states
|
||||
printVault(bob, bobStates)
|
||||
|
||||
Assertions.assertThat(bobStates).hasSize(1)
|
||||
Assertions.assertThat(bobStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS)
|
||||
Assertions.assertThat(bobQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.UNCONSUMED)
|
||||
Assertions.assertThat(bobQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.SIGNATURE)
|
||||
assertThat(bobStates).hasSize(1)
|
||||
assertThat(bobStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS)
|
||||
assertThat(bobQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.UNCONSUMED)
|
||||
assertThat(bobQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.SIGNATURE)
|
||||
|
||||
// clean-up
|
||||
keyStoreDir.close()
|
||||
|
@ -22,7 +22,7 @@ import net.corda.testing.driver.internal.internalServices
|
||||
import net.corda.testing.internal.performance.div
|
||||
import net.corda.testing.node.NotarySpec
|
||||
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.performance.startPublishingFixedRateInjector
|
||||
import net.corda.testing.node.internal.performance.startReporter
|
||||
@ -97,7 +97,7 @@ class NodePerformanceTests {
|
||||
internalDriver(
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, rpcUsers = listOf(user))),
|
||||
startNodesInProcess = true,
|
||||
cordappsForAllNodes = cordappsInCurrentAndAdditionalPackages("net.corda.finance")
|
||||
cordappsForAllNodes = FINANCE_CORDAPPS
|
||||
) {
|
||||
val notary = defaultNotaryNode.getOrThrow() as InProcess
|
||||
val metricRegistry = startReporter(this.shutdownManager, notary.internalServices.monitoringService.metrics)
|
||||
|
@ -18,12 +18,14 @@ import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.NodeParameters
|
||||
import net.corda.testing.driver.driver
|
||||
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.TestCordappDirectories
|
||||
import net.corda.testing.node.internal.cordappForClasses
|
||||
import net.test.cordapp.v1.Record
|
||||
import net.test.cordapp.v1.SendMessageFlow
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Test
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
@ -46,8 +48,8 @@ class FlowCheckpointVersionNodeStartupCheckTest {
|
||||
|
||||
@Test
|
||||
fun `restart node successfully with suspended flow`() {
|
||||
return driver(parametersForRestartingNodes(listOf(defaultCordapp))) {
|
||||
createSuspendedFlowInBob(cordapps = emptySet())
|
||||
return driver(parametersForRestartingNodes()) {
|
||||
createSuspendedFlowInBob(setOf(defaultCordapp))
|
||||
// Bob will resume the flow
|
||||
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
|
||||
startNode(providedName = BOB_NAME).getOrThrow()
|
||||
@ -66,19 +68,20 @@ class FlowCheckpointVersionNodeStartupCheckTest {
|
||||
@Test
|
||||
fun `restart node with incompatible version of suspended flow due to different jar name`() {
|
||||
driver(parametersForRestartingNodes()) {
|
||||
val cordapp = defaultCordapp.withName("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
|
||||
// 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") }
|
||||
val uniqueName = "different-jar-name-test-${UUID.randomUUID()}"
|
||||
val cordapp = defaultCordapp.copy(name = uniqueName)
|
||||
|
||||
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.
|
||||
cordappJar.moveTo(cordappDir / "renamed-${cordappJar.fileName}")
|
||||
cordappJar.moveTo(cordappsDir / "renamed-${cordappJar.fileName}")
|
||||
|
||||
assertBobFailsToStartWithLogMessage(
|
||||
setOf(cordapp),
|
||||
emptyList(),
|
||||
CheckpointIncompatibleException.FlowNotInstalledException(SendMessageFlow::class.java).message
|
||||
)
|
||||
}
|
||||
@ -87,25 +90,30 @@ class FlowCheckpointVersionNodeStartupCheckTest {
|
||||
@Test
|
||||
fun `restart node with incompatible version of suspended flow due to different jar hash`() {
|
||||
driver(parametersForRestartingNodes()) {
|
||||
val originalCordapp = defaultCordapp.withName("different-jar-hash-test-${UUID.randomUUID()}")
|
||||
val originalCordappJar = TestCordappDirectories.getJarDirectory(originalCordapp).list().single { it.toString().endsWith(".jar") }
|
||||
val uniqueName = "different-jar-hash-test-${UUID.randomUUID()}"
|
||||
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 modifiedCordapp = originalCordapp.withVendor("${originalCordapp.vendor}-modified")
|
||||
val modifiedCordappJar = TestCordappDirectories.getJarDirectory(modifiedCordapp).list().single { it.toString().endsWith(".jar") }
|
||||
modifiedCordappJar.moveTo(originalCordappJar, REPLACE_EXISTING)
|
||||
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)
|
||||
|
||||
// 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(
|
||||
setOf(originalCordapp),
|
||||
emptyList(),
|
||||
// The part of the log message generated by CheckpointIncompatibleException.FlowVersionIncompatibleException
|
||||
"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)
|
||||
.map { startNode(NodeParameters(providedName = it, additionalCordapps = cordapps)) }
|
||||
.transpose()
|
||||
@ -115,6 +123,7 @@ class FlowCheckpointVersionNodeStartupCheckTest {
|
||||
// Wait until Bob progresses as far as possible because Alice node is offline
|
||||
flowTracker.takeFirst { it == SendMessageFlow.Companion.FINALISING_TRANSACTION.label }.toBlocking().single()
|
||||
bob.stop()
|
||||
return bob.baseDirectory
|
||||
}
|
||||
|
||||
private fun DriverDSL.assertBobFailsToStartWithLogMessage(cordapps: Collection<TestCordapp>, logMessage: String) {
|
||||
@ -122,8 +131,7 @@ class FlowCheckpointVersionNodeStartupCheckTest {
|
||||
startNode(NodeParameters(
|
||||
providedName = BOB_NAME,
|
||||
customOverrides = mapOf("devMode" to false),
|
||||
additionalCordapps = cordapps,
|
||||
regenerateCordappsOnStart = true
|
||||
additionalCordapps = cordapps
|
||||
)).getOrThrow()
|
||||
}
|
||||
|
||||
@ -133,11 +141,11 @@ class FlowCheckpointVersionNodeStartupCheckTest {
|
||||
assertEquals(1, matchingLineCount)
|
||||
}
|
||||
|
||||
private fun parametersForRestartingNodes(cordappsForAllNodes: List<TestCordapp> = emptyList()): DriverParameters {
|
||||
private fun parametersForRestartingNodes(): DriverParameters {
|
||||
return DriverParameters(
|
||||
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
|
||||
cordappsForAllNodes = cordappsForAllNodes
|
||||
cordappsForAllNodes = emptyList()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ import net.corda.testing.core.singleIdentity
|
||||
import net.corda.testing.driver.internal.incrementalPortAllocation
|
||||
import net.corda.testing.internal.DEV_ROOT_CA
|
||||
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.internalDriver
|
||||
import net.corda.testing.node.internal.network.NetworkMapServer
|
||||
@ -88,7 +88,7 @@ class NodeRegistrationTest {
|
||||
portAllocation = portAllocation,
|
||||
compatibilityZone = compatibilityZone,
|
||||
notarySpecs = listOf(NotarySpec(notaryName)),
|
||||
cordappsForAllNodes = cordappsInCurrentAndAdditionalPackages("net.corda.finance"),
|
||||
cordappsForAllNodes = FINANCE_CORDAPPS,
|
||||
notaryCustomOverrides = mapOf("devMode" to false)
|
||||
) {
|
||||
val (alice, genevieve) = listOf(
|
||||
|
@ -5,8 +5,7 @@ import net.corda.core.flows.*
|
||||
import net.corda.core.internal.packageName
|
||||
import net.corda.node.VersionInfo
|
||||
import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES
|
||||
import net.corda.testing.node.internal.TestCordappDirectories
|
||||
import net.corda.testing.node.internal.cordappForPackages
|
||||
import net.corda.testing.node.internal.cordappWithPackages
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Test
|
||||
import java.nio.file.Paths
|
||||
@ -68,8 +67,8 @@ class JarScanningCordappLoaderTest {
|
||||
|
||||
@Test
|
||||
fun `flows are loaded by loader`() {
|
||||
val dir = TestCordappDirectories.getJarDirectory(cordappForPackages(javaClass.packageName))
|
||||
val loader = JarScanningCordappLoader.fromDirectories(listOf(dir))
|
||||
val jarFile = cordappWithPackages(javaClass.packageName).jarFile
|
||||
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jarFile.toUri().toURL()))
|
||||
|
||||
// One cordapp from this source tree. In gradle it will also pick up the node jar.
|
||||
assertThat(loader.cordapps).isNotEmpty
|
||||
|
@ -37,16 +37,13 @@ class FinalityHandlerTest {
|
||||
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
|
||||
// CorDapp. Bob's FinalityHandler will error when validating the tx.
|
||||
val alice = mockNet.createNode(InternalMockNodeParameters(
|
||||
legalName = ALICE_NAME,
|
||||
additionalCordapps = setOf(FINANCE_CORDAPP)
|
||||
))
|
||||
val alice = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME, additionalCordapps = FINANCE_CORDAPPS))
|
||||
|
||||
var bob = mockNet.createNode(InternalMockNodeParameters(
|
||||
legalName = BOB_NAME,
|
||||
// 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.
|
||||
additionalCordapps = setOf(cordappForPackages(javaClass.packageName).withTargetVersion(3))
|
||||
additionalCordapps = setOf(cordappWithPackages(javaClass.packageName).copy(targetPlatformVersion = 3))
|
||||
))
|
||||
|
||||
val stx = alice.issueCashTo(bob)
|
||||
@ -67,15 +64,12 @@ class FinalityHandlerTest {
|
||||
|
||||
@Test
|
||||
fun `disabled if there are no old CorDapps loaded`() {
|
||||
val alice = mockNet.createNode(InternalMockNodeParameters(
|
||||
legalName = ALICE_NAME,
|
||||
additionalCordapps = setOf(FINANCE_CORDAPP)
|
||||
))
|
||||
val alice = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME, additionalCordapps = FINANCE_CORDAPPS))
|
||||
|
||||
val bob = mockNet.createNode(InternalMockNodeParameters(
|
||||
legalName = BOB_NAME,
|
||||
// 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)
|
||||
|
@ -66,7 +66,7 @@ class FlowFrameworkTests {
|
||||
@Before
|
||||
fun setUpMockNet() {
|
||||
mockNet = InternalMockNetwork(
|
||||
cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts") + FINANCE_CORDAPP,
|
||||
cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts") + FINANCE_CORDAPPS,
|
||||
servicePeerAllocationStrategy = RoundRobin()
|
||||
)
|
||||
|
||||
|
@ -3,7 +3,7 @@ package net.corda.serialization.internal.amqp.custom
|
||||
import net.corda.serialization.internal.amqp.SerializerFactory
|
||||
import org.hamcrest.CoreMatchers.`is`
|
||||
import org.hamcrest.CoreMatchers.nullValue
|
||||
import org.junit.Assert
|
||||
import org.junit.Assert.assertThat
|
||||
import org.junit.Test
|
||||
import org.mockito.Mockito
|
||||
import java.util.*
|
||||
@ -13,27 +13,27 @@ class OptionalSerializerTest {
|
||||
fun `should convert optional with item to proxy`() {
|
||||
val opt = Optional.of("GenericTestString")
|
||||
val proxy = OptionalSerializer(Mockito.mock(SerializerFactory::class.java)).toProxy(opt)
|
||||
Assert.assertThat(proxy.item, `is`<Any>("GenericTestString"))
|
||||
assertThat(proxy.item, `is`<Any>("GenericTestString"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should convert optional without item to empty proxy`() {
|
||||
val opt = Optional.ofNullable<String>(null)
|
||||
val proxy = OptionalSerializer(Mockito.mock(SerializerFactory::class.java)).toProxy(opt)
|
||||
Assert.assertThat(proxy.item, `is`(nullValue()))
|
||||
assertThat(proxy.item, `is`(nullValue()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should convert proxy without item to empty optional `() {
|
||||
val proxy = OptionalSerializer.OptionalProxy(null)
|
||||
val opt = OptionalSerializer(Mockito.mock(SerializerFactory::class.java)).fromProxy(proxy)
|
||||
Assert.assertThat(opt.isPresent, `is`(false))
|
||||
assertThat(opt.isPresent, `is`(false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should convert proxy with item to empty optional `() {
|
||||
val proxy = OptionalSerializer.OptionalProxy("GenericTestString")
|
||||
val opt = OptionalSerializer(Mockito.mock(SerializerFactory::class.java)).fromProxy(proxy)
|
||||
Assert.assertThat(opt.get(), `is`<Any>("GenericTestString"))
|
||||
assertThat(opt.get(), `is`<Any>("GenericTestString"))
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,8 @@ import net.corda.core.DoNotImplement
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.flows.FlowLogic
|
||||
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.node.NetworkParameters
|
||||
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.TestCordapp
|
||||
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 java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
@ -136,7 +141,8 @@ constructor(
|
||||
val startJmxHttpServer: Boolean = false,
|
||||
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)
|
||||
|
||||
/** 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,
|
||||
startNodesInProcess = defaultParameters.startNodesInProcess,
|
||||
waitForAllNodesToFinish = defaultParameters.waitForAllNodesToFinish,
|
||||
extraCordappPackagesToScan = defaultParameters.extraCordappPackagesToScan,
|
||||
notarySpecs = defaultParameters.notarySpecs,
|
||||
jmxPolicy = defaultParameters.jmxPolicy,
|
||||
compatibilityZone = null,
|
||||
networkParameters = defaultParameters.networkParameters,
|
||||
notaryCustomOverrides = defaultParameters.notaryCustomOverrides,
|
||||
inMemoryDB = defaultParameters.inMemoryDB,
|
||||
cordappsForAllNodes = defaultParameters.cordappsForAllNodes(),
|
||||
signCordapps = false
|
||||
cordappsForAllNodes = uncheckedCast(defaultParameters.cordappsForAllNodes)
|
||||
),
|
||||
coerce = { it },
|
||||
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.
|
||||
* Defaults to a simple validating notary.
|
||||
* @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 networkParameters The network parameters to be used by all the nodes. [NetworkParameters.notaries] must be
|
||||
* empty as notaries are defined by [notarySpecs].
|
||||
@ -225,7 +232,7 @@ fun <A> driver(defaultParameters: DriverParameters = DriverParameters(), dsl: Dr
|
||||
@Suppress("unused")
|
||||
data class DriverParameters(
|
||||
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 debugPortAllocation: PortAllocation = incrementalPortAllocation(5005),
|
||||
val systemProperties: Map<String, String> = emptyMap(),
|
||||
@ -233,6 +240,8 @@ data class DriverParameters(
|
||||
val startNodesInProcess: Boolean = false,
|
||||
val waitForAllNodesToFinish: Boolean = false,
|
||||
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(),
|
||||
@Suppress("DEPRECATION") val jmxPolicy: JmxPolicy = JmxPolicy(),
|
||||
val networkParameters: NetworkParameters = testNetworkParameters(notaries = emptyList()),
|
||||
@ -242,7 +251,7 @@ data class DriverParameters(
|
||||
) {
|
||||
constructor(
|
||||
isDebug: Boolean = false,
|
||||
driverDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()),
|
||||
driverDirectory: Path = Paths.get("build") / "node-driver" / getTimestampAsDirectoryName(),
|
||||
portAllocation: PortAllocation = incrementalPortAllocation(10000),
|
||||
debugPortAllocation: PortAllocation = incrementalPortAllocation(5005),
|
||||
systemProperties: Map<String, String> = emptyMap(),
|
||||
|
@ -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 additionalCordapps Additional [TestCordapp]s that this node will have available, in addition to the ones common to all nodes
|
||||
* 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")
|
||||
data class NodeParameters(
|
||||
@ -32,7 +30,6 @@ data class NodeParameters(
|
||||
val startInSameProcess: Boolean? = null,
|
||||
val maximumHeapSize: String = "512m",
|
||||
val additionalCordapps: Collection<TestCordapp> = emptySet(),
|
||||
val regenerateCordappsOnStart: Boolean = false,
|
||||
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 withMaximumHeapSize(maximumHeapSize: String): NodeParameters = copy(maximumHeapSize = maximumHeapSize)
|
||||
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)
|
||||
|
||||
constructor(
|
||||
|
@ -7,6 +7,7 @@ import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowSession
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.NodeInfo
|
||||
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.testing.common.internal.testNetworkParameters
|
||||
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 java.math.BigInteger
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.Future
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
@ -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 networkParameters The network parameters to be used by all the nodes. [NetworkParameters.notaries] must be
|
||||
* empty as notaries are defined by [notarySpecs].
|
||||
* @property cordappsForAllNodes [TestCordapp]s added to all nodes.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
data class MockNetworkParameters(
|
||||
@ -84,13 +88,32 @@ data class MockNetworkParameters(
|
||||
val threadPerNode: Boolean = false,
|
||||
val servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random(),
|
||||
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 withNetworkSendManuallyPumped(networkSendManuallyPumped: Boolean): MockNetworkParameters = copy(networkSendManuallyPumped = networkSendManuallyPumped)
|
||||
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 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 networkParameters The network parameters to be used by all the nodes. [NetworkParameters.notaries] must be
|
||||
* 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")
|
||||
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 defaultParameters: MockNetworkParameters = MockNetworkParameters(),
|
||||
val networkSendManuallyPumped: Boolean = defaultParameters.networkSendManuallyPumped,
|
||||
val threadPerNode: Boolean = defaultParameters.threadPerNode,
|
||||
val servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy,
|
||||
val notarySpecs: List<MockNetworkNotarySpec> = defaultParameters.notarySpecs,
|
||||
val networkParameters: NetworkParameters = defaultParameters.networkParameters,
|
||||
val cordappsForAllNodes: Collection<TestCordapp> = cordappsForPackages(cordappPackages)) {
|
||||
|
||||
val networkParameters: NetworkParameters = defaultParameters.networkParameters
|
||||
) {
|
||||
@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
|
||||
constructor(cordappPackages: List<String>, parameters: MockNetworkParameters = MockNetworkParameters()) : this(cordappPackages, defaultParameters = parameters)
|
||||
|
||||
constructor(
|
||||
cordappPackages: List<String>,
|
||||
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))
|
||||
@JvmOverloads
|
||||
constructor(parameters: MockNetworkParameters = MockNetworkParameters()) : this(emptyList(), parameters)
|
||||
|
||||
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. */
|
||||
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,
|
||||
* 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 extraCordappPackages Extra CorDapp packages to add for this node.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun createNode(legalName: CordaX500Name? = null,
|
||||
forcedID: Int? = null,
|
||||
entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
|
||||
configOverrides: MockNodeConfigOverrides? = null,
|
||||
extraCordappPackages: List<String> = emptyList()): StartedMockNode {
|
||||
|
||||
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)))
|
||||
configOverrides: MockNodeConfigOverrides? = null): StartedMockNode {
|
||||
return createNode(MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides))
|
||||
}
|
||||
|
||||
/** Create an unstarted node with the given parameters. **/
|
||||
fun createUnstartedNode(parameters: MockNodeParameters = MockNodeParameters()): UnstartedMockNode = 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))
|
||||
fun createUnstartedNode(parameters: MockNodeParameters = MockNodeParameters()): UnstartedMockNode {
|
||||
return UnstartedMockNode.create(internalMockNetwork.createUnstartedNode(InternalMockNodeParameters(parameters)))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -408,15 +395,13 @@ open class MockNetwork(
|
||||
* @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].
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun createUnstartedNode(legalName: CordaX500Name? = null,
|
||||
forcedID: Int? = null,
|
||||
entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
|
||||
configOverrides: MockNodeConfigOverrides? = null,
|
||||
additionalCordapps: Collection<TestCordapp>): UnstartedMockNode {
|
||||
val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, additionalCordapps)
|
||||
return UnstartedMockNode.create(internalMockNetwork.createUnstartedNode(InternalMockNodeParameters(parameters)))
|
||||
configOverrides: MockNodeConfigOverrides? = null): UnstartedMockNode {
|
||||
return createUnstartedNode(MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides))
|
||||
}
|
||||
|
||||
/** Start all nodes that aren't already started. **/
|
||||
|
@ -33,7 +33,6 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||
import net.corda.nodeapi.internal.persistence.contextTransaction
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.common.internal.addNotary
|
||||
import net.corda.testing.core.TestIdentity
|
||||
import net.corda.testing.internal.DEV_ROOT_CA
|
||||
import net.corda.testing.internal.MockCordappProvider
|
||||
@ -76,10 +75,8 @@ open class MockServices private constructor(
|
||||
) : ServiceHub {
|
||||
|
||||
companion object {
|
||||
|
||||
private fun cordappLoaderForPackages(packages: Iterable<String>, versionInfo: VersionInfo = VersionInfo.UNKNOWN): CordappLoader {
|
||||
val cordappPaths = cordappsForPackages(packages).map { TestCordappDirectories.getJarDirectory(it) }
|
||||
return JarScanningCordappLoader.fromDirectories(cordappPaths, versionInfo)
|
||||
return JarScanningCordappLoader.fromJarUrls(cordappsForPackages(packages).map { it.jarFile.toUri().toURL() }, versionInfo)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,88 +1,41 @@
|
||||
package net.corda.testing.node
|
||||
|
||||
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.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
|
||||
interface TestCordapp {
|
||||
/** Returns the name, defaults to "test-name" if not specified. */
|
||||
val name: String
|
||||
/** The package used to find the CorDapp. This may not be the root package of the CorDapp. */
|
||||
val scanPackage: String
|
||||
|
||||
/** Returns the title, defaults to "test-title" 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. */
|
||||
/** Returns the config for on this CorDapp, defaults to empty if not specified. */
|
||||
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. */
|
||||
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 {
|
||||
companion object {
|
||||
/**
|
||||
* 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.
|
||||
* Scans the current classpath to find the CorDapp that contains the given package. All the CorDapp's metdata present in its
|
||||
* 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
|
||||
fun fromPackages(vararg packageNames: String): TestCordapp = fromPackages(packageNames.asList())
|
||||
|
||||
/**
|
||||
* 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()
|
||||
)
|
||||
}
|
||||
fun findCordapp(scanPackage: String): TestCordapp = TestCordappImpl(scanPackage = scanPackage, config = emptyMap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -27,7 +27,6 @@ import net.corda.node.internal.NodeWithInfo
|
||||
import net.corda.node.internal.clientSslOptionsCompatibleWith
|
||||
import net.corda.node.services.Permissions
|
||||
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.NodeRegistrationHelper
|
||||
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.node.ClusterSpec
|
||||
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.Request
|
||||
import rx.Subscription
|
||||
@ -73,6 +70,7 @@ import java.util.concurrent.TimeoutException
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.HashMap
|
||||
import kotlin.collections.HashSet
|
||||
import kotlin.concurrent.thread
|
||||
import net.corda.nodeapi.internal.config.User as InternalUser
|
||||
|
||||
@ -85,20 +83,21 @@ class DriverDSLImpl(
|
||||
val isDebug: Boolean,
|
||||
val startNodesInProcess: Boolean,
|
||||
val waitForAllNodesToFinish: Boolean,
|
||||
val extraCordappPackagesToScan: List<String>,
|
||||
val jmxPolicy: JmxPolicy,
|
||||
val notarySpecs: List<NotarySpec>,
|
||||
val compatibilityZone: CompatibilityZoneParams?,
|
||||
val networkParameters: NetworkParameters,
|
||||
val notaryCustomOverrides: Map<String, Any?>,
|
||||
val inMemoryDB: Boolean,
|
||||
val cordappsForAllNodes: Collection<TestCordapp>,
|
||||
val signCordapps: Boolean
|
||||
val cordappsForAllNodes: Collection<TestCordappInternal>?
|
||||
) : InternalDriverDSL {
|
||||
|
||||
private var _executorService: ScheduledExecutorService? = null
|
||||
val executorService get() = _executorService!!
|
||||
private var _shutdownManager: ShutdownManager? = null
|
||||
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.
|
||||
private val networkVisibilityController = NetworkVisibilityController()
|
||||
/**
|
||||
@ -198,7 +197,7 @@ class DriverDSLImpl(
|
||||
return registrationFuture.flatMap {
|
||||
networkMapAvailability.flatMap {
|
||||
// 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,
|
||||
localNetworkMap: LocalNetworkMap?,
|
||||
parameters: NodeParameters,
|
||||
p2pAddress: NetworkHostAndPort = portAllocation.nextHostAndPort(),
|
||||
signCordapps: Boolean = false): CordaFuture<NodeHandle> {
|
||||
p2pAddress: NetworkHostAndPort = portAllocation.nextHostAndPort()): CordaFuture<NodeHandle> {
|
||||
val rpcAddress = portAllocation.nextHostAndPort()
|
||||
val rpcAdminAddress = 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 overrides = configOf(
|
||||
NodeConfiguration::myLegalName.name to name.toString(),
|
||||
NodeConfiguration::p2pAddress.name to p2pAddress.toString(),
|
||||
@ -245,7 +243,7 @@ class DriverDSLImpl(
|
||||
allowMissingConfig = true,
|
||||
configOverrides = if (overrides.hasPath("devMode")) overrides else overrides + mapOf("devMode" to true)
|
||||
)).checkAndOverrideForInMemoryDB()
|
||||
return startNodeInternal(config, webAddress, localNetworkMap, parameters, signCordapps)
|
||||
return startNodeInternal(config, webAddress, localNetworkMap, parameters)
|
||||
}
|
||||
|
||||
private fun startNodeRegistration(
|
||||
@ -333,6 +331,9 @@ class DriverDSLImpl(
|
||||
require(networkParameters.notaries.isEmpty()) { "Define notaries using notarySpecs" }
|
||||
_executorService = Executors.newScheduledThreadPool(2, ThreadFactoryBuilder().setNameFormat("driver-pool-thread-%d").build())
|
||||
_shutdownManager = ShutdownManager(executorService)
|
||||
|
||||
extraCustomCordapps = cordappsForPackages(extraCordappPackagesToScan + getCallerPackage())
|
||||
|
||||
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
|
||||
// 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>> {
|
||||
return executorService.fork {
|
||||
notarySpecs.map { spec ->
|
||||
@ -531,13 +553,12 @@ class DriverDSLImpl(
|
||||
}
|
||||
}
|
||||
|
||||
private fun startNodeInternal(specifiedConfig: NodeConfig,
|
||||
private fun startNodeInternal(config: NodeConfig,
|
||||
webAddress: NetworkHostAndPort,
|
||||
localNetworkMap: LocalNetworkMap?,
|
||||
parameters: NodeParameters,
|
||||
signCordapps: Boolean = false): CordaFuture<NodeHandle> {
|
||||
val visibilityHandle = networkVisibilityController.register(specifiedConfig.corda.myLegalName)
|
||||
val baseDirectory = specifiedConfig.corda.baseDirectory.createDirectories()
|
||||
parameters: NodeParameters): CordaFuture<NodeHandle> {
|
||||
val visibilityHandle = networkVisibilityController.register(config.corda.myLegalName)
|
||||
val baseDirectory = config.corda.baseDirectory.createDirectories()
|
||||
localNetworkMap?.networkParametersCopier?.install(baseDirectory)
|
||||
localNetworkMap?.nodeInfosCopier?.addConfig(baseDirectory)
|
||||
|
||||
@ -546,24 +567,13 @@ class DriverDSLImpl(
|
||||
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) {
|
||||
emptyList()
|
||||
} else if (specifiedConfig.typesafe.hasPath(cordappDirectoriesKey)) {
|
||||
specifiedConfig.typesafe.getStringList(cordappDirectoriesKey)
|
||||
} 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())))
|
||||
TestCordappInternal.installCordapps(
|
||||
baseDirectory,
|
||||
parameters.additionalCordapps.mapTo(HashSet()) { it as TestCordappInternal },
|
||||
extraCustomCordapps + (cordappsForAllNodes ?: emptySet())
|
||||
)
|
||||
|
||||
if (parameters.startInSameProcess ?: startNodesInProcess) {
|
||||
val nodeAndThreadFuture = startInProcessNode(executorService, config)
|
||||
@ -685,14 +695,6 @@ class DriverDSLImpl(
|
||||
|
||||
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(
|
||||
executorService: ScheduledExecutorService,
|
||||
config: NodeConfig
|
||||
@ -818,22 +820,6 @@ class DriverDSLImpl(
|
||||
|
||||
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
|
||||
* 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,
|
||||
startNodesInProcess = defaultParameters.startNodesInProcess,
|
||||
waitForAllNodesToFinish = defaultParameters.waitForAllNodesToFinish,
|
||||
extraCordappPackagesToScan = defaultParameters.extraCordappPackagesToScan,
|
||||
jmxPolicy = defaultParameters.jmxPolicy,
|
||||
notarySpecs = defaultParameters.notarySpecs,
|
||||
compatibilityZone = null,
|
||||
networkParameters = defaultParameters.networkParameters,
|
||||
notaryCustomOverrides = defaultParameters.notaryCustomOverrides,
|
||||
inMemoryDB = defaultParameters.inMemoryDB,
|
||||
cordappsForAllNodes = defaultParameters.cordappsForAllNodes(),
|
||||
signCordapps = false
|
||||
cordappsForAllNodes = uncheckedCast(defaultParameters.cordappsForAllNodes)
|
||||
)
|
||||
)
|
||||
val shutdownHook = addShutdownHook(driverDsl::shutdown)
|
||||
@ -1089,6 +1075,7 @@ fun <A> internalDriver(
|
||||
systemProperties: Map<String, String> = DriverParameters().systemProperties,
|
||||
useTestClock: Boolean = DriverParameters().useTestClock,
|
||||
startNodesInProcess: Boolean = DriverParameters().startNodesInProcess,
|
||||
extraCordappPackagesToScan: List<String> = DriverParameters().extraCordappPackagesToScan,
|
||||
waitForAllNodesToFinish: Boolean = DriverParameters().waitForAllNodesToFinish,
|
||||
notarySpecs: List<NotarySpec> = DriverParameters().notarySpecs,
|
||||
jmxPolicy: JmxPolicy = DriverParameters().jmxPolicy,
|
||||
@ -1096,8 +1083,7 @@ fun <A> internalDriver(
|
||||
compatibilityZone: CompatibilityZoneParams? = null,
|
||||
notaryCustomOverrides: Map<String, Any?> = DriverParameters().notaryCustomOverrides,
|
||||
inMemoryDB: Boolean = DriverParameters().inMemoryDB,
|
||||
cordappsForAllNodes: Collection<TestCordapp> = DriverParameters().cordappsForAllNodes(),
|
||||
signCordapps: Boolean = false,
|
||||
cordappsForAllNodes: Collection<TestCordappInternal>? = null,
|
||||
dsl: DriverDSLImpl.() -> A
|
||||
): A {
|
||||
return genericDriver(
|
||||
@ -1110,14 +1096,14 @@ fun <A> internalDriver(
|
||||
isDebug = isDebug,
|
||||
startNodesInProcess = startNodesInProcess,
|
||||
waitForAllNodesToFinish = waitForAllNodesToFinish,
|
||||
extraCordappPackagesToScan = extraCordappPackagesToScan,
|
||||
notarySpecs = notarySpecs,
|
||||
jmxPolicy = jmxPolicy,
|
||||
compatibilityZone = compatibilityZone,
|
||||
networkParameters = networkParameters,
|
||||
notaryCustomOverrides = notaryCustomOverrides,
|
||||
inMemoryDB = inMemoryDB,
|
||||
cordappsForAllNodes = cordappsForAllNodes,
|
||||
signCordapps = signCordapps
|
||||
cordappsForAllNodes = cordappsForAllNodes
|
||||
),
|
||||
coerce = { it },
|
||||
dsl = dsl
|
||||
@ -1137,9 +1123,6 @@ private fun Config.toNodeOnly(): Config {
|
||||
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> {
|
||||
val customOverrides = if (!devMode) {
|
||||
val nodeDir = baseDirectory(providedName)
|
||||
|
@ -92,7 +92,7 @@ data class InternalMockNodeParameters(
|
||||
val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
|
||||
val configOverrides: MockNodeConfigOverrides? = null,
|
||||
val version: VersionInfo = MOCK_VERSION_INFO,
|
||||
val additionalCordapps: Collection<TestCordapp>? = null,
|
||||
val additionalCordapps: Collection<TestCordappInternal> = emptyList(),
|
||||
val flowManager: MockNodeFlowManager = MockNodeFlowManager()) {
|
||||
constructor(mockNodeParameters: MockNodeParameters) : this(
|
||||
mockNodeParameters.forcedID,
|
||||
@ -100,7 +100,7 @@ data class InternalMockNodeParameters(
|
||||
mockNodeParameters.entropyRoot,
|
||||
mockNodeParameters.configOverrides,
|
||||
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>
|
||||
}
|
||||
|
||||
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 threadPerNode: Boolean = defaultParameters.threadPerNode,
|
||||
servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy,
|
||||
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(),
|
||||
val defaultFactory: (MockNodeArgs) -> MockNode = { args -> MockNode(args) },
|
||||
val cordappsForAllNodes: Collection<TestCordapp> = emptySet(),
|
||||
cordappsForAllNodes: Collection<TestCordappInternal> = emptySet(),
|
||||
val autoVisibleNodes: Boolean = true) : AutoCloseable {
|
||||
|
||||
var networkParameters: NetworkParameters = initialNetworkParameters
|
||||
@ -174,6 +176,7 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
|
||||
private val _nodes = mutableListOf<MockNode>()
|
||||
private val serializationEnv = checkNotNull(setDriverSerialization()) { "Using more than one mock network simultaneously is not supported." }
|
||||
private val sharedUserCount = AtomicInteger(0)
|
||||
private val combinedCordappsForAllNodes = cordappsForPackages(cordappPackages) + cordappsForAllNodes
|
||||
|
||||
/** A read only view of the current set of 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(makeTestDataSourceProperties("node_${id}_net_$networkId")).whenever(it).dataSourceProperties
|
||||
doReturn(emptyList<SecureHash>()).whenever(it).extraNetworkMapKeys
|
||||
doReturn(listOf(baseDirectory / "cordapps")).whenever(it).cordappDirectories
|
||||
parameters.configOverrides?.applyMockNodeOverrides(it)
|
||||
}
|
||||
|
||||
val cordapps = (parameters.additionalCordapps ?: emptySet()) + cordappsForAllNodes
|
||||
val cordappDirectories = cordapps.map { TestCordappDirectories.getJarDirectory(it) }.distinct()
|
||||
doReturn(cordappDirectories).whenever(config).cordappDirectories
|
||||
TestCordappInternal.installCordapps(baseDirectory, parameters.additionalCordapps.toSet(), combinedCordappsForAllNodes)
|
||||
|
||||
val node = nodeFactory(MockNodeArgs(config, this, id, parameters.entropyRoot, parameters.version, flowManager = parameters.flowManager))
|
||||
_nodes += node
|
||||
|
@ -29,11 +29,14 @@ import net.corda.testing.node.testContext
|
||||
import org.slf4j.LoggerFactory
|
||||
import rx.Observable
|
||||
import rx.subjects.AsyncSubject
|
||||
import java.io.InputStream
|
||||
import java.net.Socket
|
||||
import java.net.SocketException
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.ScheduledExecutorService
|
||||
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")
|
||||
|
||||
@ -152,3 +155,14 @@ private class DriverSerializationEnvironment : SerializationEnvironment by creat
|
||||
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()
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.corda.testing.node.internal
|
||||
|
||||
import com.typesafe.config.ConfigValueFactory
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.PLATFORM_VERSION
|
||||
@ -121,16 +120,10 @@ constructor(private val cordappPackages: List<String> = emptyList(), private val
|
||||
) + configOverrides
|
||||
)
|
||||
|
||||
val cordapps = cordappsForPackages(getCallerPackage(NodeBasedTest::class)?.let { cordappPackages + it }
|
||||
?: cordappPackages)
|
||||
val customCordapps = cordappsForPackages(getCallerPackage(NodeBasedTest::class)?.let { cordappPackages + it } ?: cordappPackages)
|
||||
TestCordappInternal.installCordapps(baseDirectory, emptySet(), customCordapps)
|
||||
|
||||
val existingCorDappDirectoriesOption = if (config.hasPath(NodeConfiguration.cordappDirectoriesKey)) config.getStringList(NodeConfiguration.cordappDirectoriesKey) else emptyList()
|
||||
|
||||
val cordappDirectories = existingCorDappDirectoriesOption + cordapps.map { TestCordappDirectories.getJarDirectory(it).toString() }
|
||||
|
||||
val specificConfig = config.withValue(NodeConfiguration.cordappDirectoriesKey, ConfigValueFactory.fromIterable(cordappDirectories.toSet()))
|
||||
|
||||
val parsedConfig = specificConfig.parseAsNodeConfiguration().value()
|
||||
val parsedConfig = config.parseAsNodeConfiguration().value()
|
||||
|
||||
defaultNetworkParameters.install(baseDirectory)
|
||||
val node = InProcessNode(parsedConfig, MOCK_VERSION_INFO.copy(platformVersion = platformVersion), flowManager = flowManager)
|
||||
|
@ -32,9 +32,7 @@ import net.corda.testing.driver.internal.incrementalPortAllocation
|
||||
import net.corda.testing.internal.TestingNamedCacheFactory
|
||||
import net.corda.testing.internal.fromUserList
|
||||
import net.corda.testing.node.NotarySpec
|
||||
import net.corda.testing.node.TestCordapp
|
||||
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.TransportConfiguration
|
||||
import org.apache.activemq.artemis.api.core.client.ActiveMQClient
|
||||
@ -108,20 +106,21 @@ private val globalDebugPortAllocation = incrementalPortAllocation(5005)
|
||||
|
||||
fun <A> rpcDriver(
|
||||
isDebug: Boolean = false,
|
||||
driverDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()),
|
||||
driverDirectory: Path = Paths.get("build") / "rpc-driver" / getTimestampAsDirectoryName(),
|
||||
portAllocation: PortAllocation = globalPortAllocation,
|
||||
debugPortAllocation: PortAllocation = globalDebugPortAllocation,
|
||||
systemProperties: Map<String, String> = emptyMap(),
|
||||
useTestClock: Boolean = false,
|
||||
startNodesInProcess: Boolean = false,
|
||||
waitForNodesToFinish: Boolean = false,
|
||||
extraCordappPackagesToScan: List<String> = emptyList(),
|
||||
notarySpecs: List<NotarySpec> = emptyList(),
|
||||
externalTrace: Trace? = null,
|
||||
@Suppress("DEPRECATION") jmxPolicy: JmxPolicy = JmxPolicy(),
|
||||
networkParameters: NetworkParameters = testNetworkParameters(),
|
||||
notaryCustomOverrides: Map<String, Any?> = emptyMap(),
|
||||
inMemoryDB: Boolean = true,
|
||||
cordappsForAllNodes: Collection<TestCordapp> = cordappsInCurrentAndAdditionalPackages(),
|
||||
cordappsForAllNodes: Collection<TestCordappInternal>? = null,
|
||||
dsl: RPCDriverDSL.() -> A
|
||||
): A {
|
||||
return genericDriver(
|
||||
@ -135,14 +134,14 @@ fun <A> rpcDriver(
|
||||
isDebug = isDebug,
|
||||
startNodesInProcess = startNodesInProcess,
|
||||
waitForAllNodesToFinish = waitForNodesToFinish,
|
||||
extraCordappPackagesToScan = extraCordappPackagesToScan,
|
||||
notarySpecs = notarySpecs,
|
||||
jmxPolicy = jmxPolicy,
|
||||
compatibilityZone = null,
|
||||
networkParameters = networkParameters,
|
||||
notaryCustomOverrides = notaryCustomOverrides,
|
||||
inMemoryDB = inMemoryDB,
|
||||
cordappsForAllNodes = cordappsForAllNodes,
|
||||
signCordapps = false
|
||||
cordappsForAllNodes = cordappsForAllNodes
|
||||
), externalTrace
|
||||
),
|
||||
coerce = { it },
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -1,35 +1,99 @@
|
||||
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 org.apache.commons.lang.SystemUtils
|
||||
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,
|
||||
override val vendor: String,
|
||||
override val title: String,
|
||||
override val targetVersion: Int,
|
||||
override val config: Map<String, Any>,
|
||||
override val packages: Set<String>,
|
||||
override val signJar: Boolean = false,
|
||||
val keyStorePath: Path? = null,
|
||||
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)
|
||||
|
||||
/**
|
||||
* Implementation of the public [TestCordapp] API.
|
||||
*
|
||||
* As described in [TestCordapp.Factory.findCordapp], this represents a single CorDapp jar on the current classpath. The [scanPackage] may
|
||||
* 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,
|
||||
* the [scanPackage] may reference a gradle CorDapp project on the local system. In this scenerio the project's "jar" task is executed to
|
||||
* build the CorDapp jar. This allows us to inherit the CorDapp's MANIFEST information without having to do any extra processing.
|
||||
*/
|
||||
data class TestCordappImpl(override val scanPackage: String, override val config: Map<String, Any>) : TestCordappInternal {
|
||||
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 {
|
||||
return copy(classes = classes.filter { clazz -> packages.none { clazz.name.startsWith("$it.") } }.toSet())
|
||||
override val jarFile: Path
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -1,41 +1,52 @@
|
||||
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 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
|
||||
|
||||
/**
|
||||
* 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
|
||||
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> {
|
||||
return simplifyScanPackages(packageNames).map { cordappForPackages(it) }
|
||||
@JvmField
|
||||
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 {
|
||||
return TestCordapp.Factory.fromPackages(*packageNames) as TestCordappImpl
|
||||
}
|
||||
/**
|
||||
* Create a *custom* CorDapp which contains all the classes and resoures located in the given packages. The CorDapp's metadata will be the
|
||||
* 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<*>? {
|
||||
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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -1,22 +1,9 @@
|
||||
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.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.nio.file.Path
|
||||
import java.util.jar.JarInputStream
|
||||
|
||||
class TestCordappsUtilsTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
val tempFolder = TemporaryFolder()
|
||||
|
||||
@Test
|
||||
fun `test simplifyScanPackages`() {
|
||||
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"))).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
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,8 @@ import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.driver.*
|
||||
import net.corda.testing.driver.internal.incrementalPortAllocation
|
||||
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.util.*
|
||||
|
||||
@ -71,7 +72,7 @@ class ExplorerSimulation(private val options: OptionSet) {
|
||||
val portAllocation = incrementalPortAllocation(20000)
|
||||
driver(DriverParameters(
|
||||
portAllocation = portAllocation,
|
||||
cordappsForAllNodes = listOf(FINANCE_CORDAPP),
|
||||
cordappsForAllNodes = FINANCE_CORDAPPS,
|
||||
waitForAllNodesToFinish = true,
|
||||
jmxPolicy = JmxPolicy.defaultEnabled()
|
||||
)) {
|
||||
@ -83,12 +84,12 @@ class ExplorerSimulation(private val options: OptionSet) {
|
||||
val issuerGBP = startNode(NodeParameters(
|
||||
providedName = ukBankName,
|
||||
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(
|
||||
providedName = usaBankName,
|
||||
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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user