mirror of
https://github.com/corda/corda.git
synced 2025-06-16 14:18:20 +00:00
CORDA-2088: Simplified the TestCordapp public API (#4064)
The entry point to the API has been simplified to just requireing a list of packages to scan, with sensible defaults provided for the metadata. Because of the wither methods, having parameters for the metadata (with default values) seems unnecessary. Also the ability to scan just individual classes has been made internal, as it seems unlikely app developers would need that level of control when testing their apps. TestCordappImpl is a data class and thus acts as a natural key for the Jar caching, where previously the key was the package names. This fixes an issue where it was not possible to create two CorDapp Jars of the same package but different metadata.
This commit is contained in:
@ -4,7 +4,6 @@ import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.concurrent.transpose
|
||||
import net.corda.core.internal.packageName
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.unwrap
|
||||
@ -12,8 +11,8 @@ import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.core.singleIdentity
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.TestCorDapp
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.node.internal.cordappForClasses
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@ -46,23 +45,18 @@ class AsymmetricCorDappsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun noSharedCorDappsWithAsymmetricSpecificClasses() {
|
||||
|
||||
fun `no shared cordapps with asymmetric specific classes`() {
|
||||
driver(DriverParameters(startNodesInProcess = false, cordappsForAllNodes = emptySet())) {
|
||||
|
||||
val nodeA = startNode(providedName = ALICE_NAME, additionalCordapps = setOf(TestCorDapp.Factory.create("Szymon CorDapp", "1.0", classes = setOf(Ping::class.java)))).getOrThrow()
|
||||
val nodeB = startNode(providedName = BOB_NAME, additionalCordapps = setOf(TestCorDapp.Factory.create("Szymon CorDapp", "1.0", classes = setOf(Ping::class.java, Pong::class.java)))).getOrThrow()
|
||||
val nodeA = startNode(providedName = ALICE_NAME, additionalCordapps = setOf(cordappForClasses(Ping::class.java))).getOrThrow()
|
||||
val nodeB = startNode(providedName = BOB_NAME, additionalCordapps = setOf(cordappForClasses(Ping::class.java, Pong::class.java))).getOrThrow()
|
||||
nodeA.rpc.startFlow(::Ping, nodeB.nodeInfo.singleIdentity(), 1).returnValue.getOrThrow()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sharedCorDappsWithAsymmetricSpecificClasses() {
|
||||
|
||||
val resourceName = "cordapp.properties"
|
||||
val cordappPropertiesResource = this::class.java.getResource(resourceName)
|
||||
val sharedCordapp = TestCorDapp.Factory.create("shared", "1.0", classes = setOf(Ping::class.java)).plusResource("${AsymmetricCorDappsTests::class.java.packageName}.$resourceName", cordappPropertiesResource)
|
||||
val cordappForNodeB = TestCorDapp.Factory.create("nodeB_only", "1.0", classes = setOf(Pong::class.java))
|
||||
fun `shared cordapps with asymmetric specific classes`() {
|
||||
val sharedCordapp = cordappForClasses(Ping::class.java)
|
||||
val cordappForNodeB = cordappForClasses(Pong::class.java)
|
||||
driver(DriverParameters(startNodesInProcess = false, cordappsForAllNodes = setOf(sharedCordapp))) {
|
||||
|
||||
val (nodeA, nodeB) = listOf(startNode(providedName = ALICE_NAME), startNode(providedName = BOB_NAME, additionalCordapps = setOf(cordappForNodeB))).transpose().getOrThrow()
|
||||
@ -71,12 +65,9 @@ class AsymmetricCorDappsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sharedCorDappsWithAsymmetricSpecificClassesInProcess() {
|
||||
|
||||
val resourceName = "cordapp.properties"
|
||||
val cordappPropertiesResource = this::class.java.getResource(resourceName)
|
||||
val sharedCordapp = TestCorDapp.Factory.create("shared", "1.0", classes = setOf(Ping::class.java)).plusResource("${AsymmetricCorDappsTests::class.java.packageName}.$resourceName", cordappPropertiesResource)
|
||||
val cordappForNodeB = TestCorDapp.Factory.create("nodeB_only", "1.0", classes = setOf(Pong::class.java))
|
||||
fun `shared cordapps with asymmetric specific classes in process`() {
|
||||
val sharedCordapp = cordappForClasses(Ping::class.java)
|
||||
val cordappForNodeB = cordappForClasses(Pong::class.java)
|
||||
driver(DriverParameters(startNodesInProcess = true, cordappsForAllNodes = setOf(sharedCordapp))) {
|
||||
|
||||
val (nodeA, nodeB) = listOf(startNode(providedName = ALICE_NAME), startNode(providedName = BOB_NAME, additionalCordapps = setOf(cordappForNodeB))).transpose().getOrThrow()
|
||||
|
@ -1,27 +1,30 @@
|
||||
package net.corda.node.flows
|
||||
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.core.internal.concurrent.transpose
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.list
|
||||
import net.corda.core.internal.moveTo
|
||||
import net.corda.core.internal.readLines
|
||||
import net.corda.core.messaging.startTrackedFlow
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.node.internal.CheckpointIncompatibleException
|
||||
import net.corda.node.internal.NodeStartup
|
||||
import net.corda.node.services.Permissions.Companion.invokeRpc
|
||||
import net.corda.node.services.Permissions.Companion.startFlow
|
||||
import net.corda.testMessage.Message
|
||||
import net.corda.testMessage.MessageState
|
||||
import net.corda.testMessage.*
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.core.singleIdentity
|
||||
import net.corda.testing.driver.DriverDSL
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.TestCorDapp
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.node.User
|
||||
import net.corda.testing.node.TestCordapp
|
||||
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.junit.Test
|
||||
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
@ -30,125 +33,111 @@ import kotlin.test.assertNotNull
|
||||
class FlowCheckpointVersionNodeStartupCheckTest {
|
||||
companion object {
|
||||
val message = Message("Hello world!")
|
||||
val classes = setOf(net.corda.testMessage.MessageState::class.java,
|
||||
net.corda.testMessage.MessageContract::class.java,
|
||||
net.test.cordapp.v1.SendMessageFlow::class.java,
|
||||
net.corda.testMessage.MessageSchema::class.java,
|
||||
net.corda.testMessage.MessageSchemaV1::class.java,
|
||||
net.test.cordapp.v1.Record::class.java)
|
||||
val user = User("mark", "dadada", setOf(startFlow<SendMessageFlow>(), invokeRpc("vaultQuery"), invokeRpc("vaultTrack")))
|
||||
val defaultCordapp = cordappForClasses(
|
||||
MessageState::class.java,
|
||||
MessageContract::class.java,
|
||||
SendMessageFlow::class.java,
|
||||
MessageSchema::class.java,
|
||||
MessageSchemaV1::class.java,
|
||||
Record::class.java
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `restart node successfully with suspended flow`() {
|
||||
|
||||
val cordapps = setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes))
|
||||
|
||||
return driver(DriverParameters(isDebug = true, startNodesInProcess = false, inMemoryDB = false, cordappsForAllNodes = cordapps)) {
|
||||
{
|
||||
val alice = startNode(rpcUsers = listOf(user), providedName = ALICE_NAME).getOrThrow()
|
||||
val bob = startNode(rpcUsers = listOf(user), providedName = BOB_NAME).getOrThrow()
|
||||
alice.stop()
|
||||
CordaRPCClient(bob.rpcAddress).start(user.username, user.password).use {
|
||||
val flowTracker = it.proxy.startTrackedFlow(::SendMessageFlow, message, defaultNotaryIdentity, alice.nodeInfo.singleIdentity()).progress
|
||||
//wait until Bob progresses as far as possible because alice node is off
|
||||
flowTracker.takeFirst { it == SendMessageFlow.Companion.FINALISING_TRANSACTION.label }.toBlocking().single()
|
||||
}
|
||||
bob.stop()
|
||||
}()
|
||||
val result = {
|
||||
//Bob will resume the flow
|
||||
val alice = startNode(rpcUsers = listOf(user), providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false)).getOrThrow()
|
||||
startNode(providedName = BOB_NAME, rpcUsers = listOf(user), customOverrides = mapOf("devMode" to false)).getOrThrow()
|
||||
CordaRPCClient(alice.rpcAddress).start(user.username, user.password).use {
|
||||
val page = it.proxy.vaultTrack(MessageState::class.java)
|
||||
if (page.snapshot.states.isNotEmpty()) {
|
||||
page.snapshot.states.first()
|
||||
} else {
|
||||
val r = page.updates.timeout(5, TimeUnit.SECONDS).take(1).toBlocking().single()
|
||||
if (r.consumed.isNotEmpty()) r.consumed.first() else r.produced.first()
|
||||
}
|
||||
}
|
||||
}()
|
||||
return driver(parametersForRestartingNodes(listOf(defaultCordapp))) {
|
||||
createSuspendedFlowInBob(cordapps = emptySet())
|
||||
// Bob will resume the flow
|
||||
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
|
||||
startNode(providedName = BOB_NAME).getOrThrow()
|
||||
val page = alice.rpc.vaultTrack(MessageState::class.java)
|
||||
val result = if (page.snapshot.states.isNotEmpty()) {
|
||||
page.snapshot.states.first()
|
||||
} else {
|
||||
val r = page.updates.timeout(5, TimeUnit.SECONDS).take(1).toBlocking().single()
|
||||
if (r.consumed.isNotEmpty()) r.consumed.first() else r.produced.first()
|
||||
}
|
||||
assertNotNull(result)
|
||||
assertEquals(message, result.state.data.message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun assertNodeRestartFailure(
|
||||
cordapps: Set<TestCorDapp>?,
|
||||
cordappsVersionAtStartup: Set<TestCorDapp>,
|
||||
cordappsVersionAtRestart: Set<TestCorDapp>,
|
||||
reuseAdditionalCordappsAtRestart: Boolean,
|
||||
assertNodeLogs: String
|
||||
) {
|
||||
@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()
|
||||
|
||||
return driver(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 flow in Bob's node
|
||||
cordappsForAllNodes = cordapps)
|
||||
) {
|
||||
val bobLogFolder = {
|
||||
val alice = startNode(rpcUsers = listOf(user), providedName = ALICE_NAME, additionalCordapps = cordappsVersionAtStartup).getOrThrow()
|
||||
val bob = startNode(rpcUsers = listOf(user), providedName = BOB_NAME, additionalCordapps = cordappsVersionAtStartup).getOrThrow()
|
||||
alice.stop()
|
||||
CordaRPCClient(bob.rpcAddress).start(user.username, user.password).use {
|
||||
val flowTracker = it.proxy.startTrackedFlow(::SendMessageFlow, message, defaultNotaryIdentity, alice.nodeInfo.singleIdentity()).progress
|
||||
// wait until Bob progresses as far as possible because Alice node is offline
|
||||
flowTracker.takeFirst { it == SendMessageFlow.Companion.FINALISING_TRANSACTION.label }.toBlocking().single()
|
||||
}
|
||||
val logFolder = bob.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME
|
||||
// SendMessageFlow suspends in Bob node
|
||||
bob.stop()
|
||||
logFolder
|
||||
}()
|
||||
createSuspendedFlowInBob(setOf(cordapp))
|
||||
|
||||
startNode(rpcUsers = listOf(user), providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false),
|
||||
additionalCordapps = cordappsVersionAtRestart, regenerateCordappsOnStart = !reuseAdditionalCordappsAtRestart).getOrThrow()
|
||||
// Rename the jar file. TestCordappDirectories caches the location of the jar file but the use of the random
|
||||
// UUID in the name means there's zero chance of contaminating another test.
|
||||
cordappJar.moveTo(cordappDir / "renamed-${cordappJar.fileName}")
|
||||
|
||||
assertFailsWith(ListenProcessDeathException::class) {
|
||||
startNode(providedName = BOB_NAME, rpcUsers = listOf(user), customOverrides = mapOf("devMode" to false),
|
||||
additionalCordapps = cordappsVersionAtRestart, regenerateCordappsOnStart = !reuseAdditionalCordappsAtRestart).getOrThrow()
|
||||
}
|
||||
|
||||
val logFile = bobLogFolder.list { it.filter { it.fileName.toString().endsWith(".log") }.findAny().get() }
|
||||
val numberOfNodesThatLogged = logFile.readLines { it.filter { assertNodeLogs in it }.count() }
|
||||
assertEquals(1, numberOfNodesThatLogged)
|
||||
assertBobFailsToStartWithLogMessage(
|
||||
setOf(cordapp),
|
||||
CheckpointIncompatibleException.FlowNotInstalledException(SendMessageFlow::class.java).message
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `restart nodes with incompatible version of suspended flow due to different jar name`() {
|
||||
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()
|
||||
|
||||
assertNodeRestartFailure(
|
||||
emptySet(),
|
||||
setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes)),
|
||||
setOf(TestCorDapp.Factory.create("testJar2", "1.0", classes = classes)),
|
||||
false,
|
||||
CheckpointIncompatibleException.FlowNotInstalledException(SendMessageFlow::class.java).message)
|
||||
createSuspendedFlowInBob(setOf(originalCordapp))
|
||||
|
||||
// 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()
|
||||
modifiedCordappJar.moveTo(originalCordappJar, REPLACE_EXISTING)
|
||||
|
||||
assertBobFailsToStartWithLogMessage(
|
||||
setOf(originalCordapp),
|
||||
// The part of the log message generated by CheckpointIncompatibleException.FlowVersionIncompatibleException
|
||||
"that is incompatible with the current installed version of"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `restart nodes with incompatible version of suspended flow`() {
|
||||
|
||||
assertNodeRestartFailure(
|
||||
emptySet(),
|
||||
setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes)),
|
||||
setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes + net.test.cordapp.v1.SendMessageFlow::class.java)),
|
||||
false,
|
||||
// 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>) {
|
||||
val (alice, bob) = listOf(ALICE_NAME, BOB_NAME)
|
||||
.map { startNode(providedName = it, additionalCordapps = cordapps) }
|
||||
.transpose()
|
||||
.getOrThrow()
|
||||
alice.stop()
|
||||
val flowTracker = bob.rpc.startTrackedFlow(::SendMessageFlow, message, defaultNotaryIdentity, alice.nodeInfo.singleIdentity()).progress
|
||||
// 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()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `restart nodes with incompatible version of suspended flow due to different timestamps only`() {
|
||||
private fun DriverDSL.assertBobFailsToStartWithLogMessage(cordapps: Collection<TestCordapp>, logMessage: String) {
|
||||
assertFailsWith(ListenProcessDeathException::class) {
|
||||
startNode(
|
||||
providedName = BOB_NAME,
|
||||
customOverrides = mapOf("devMode" to false),
|
||||
additionalCordapps = cordapps,
|
||||
regenerateCordappsOnStart = true
|
||||
).getOrThrow()
|
||||
}
|
||||
|
||||
assertNodeRestartFailure(
|
||||
emptySet(),
|
||||
setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes)),
|
||||
setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes)),
|
||||
false,
|
||||
// the part of the log message generated by CheckpointIncompatibleException.FlowVersionIncompatibleException
|
||||
"that is incompatible with the current installed version of")
|
||||
val logDir = baseDirectory(BOB_NAME) / NodeStartup.LOGS_DIRECTORY_NAME
|
||||
val logFile = logDir.list { it.filter { it.fileName.toString().endsWith(".log") }.findAny().get() }
|
||||
val matchingLineCount = logFile.readLines { it.filter { line -> logMessage in line }.count() }
|
||||
assertEquals(1, matchingLineCount)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parametersForRestartingNodes(cordappsForAllNodes: List<TestCordapp> = emptyList()): 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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -3,17 +3,11 @@ package net.corda.node.services
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.CordaRuntimeException
|
||||
import net.corda.core.contracts.Contract
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.PartyAndReference
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.TransactionState
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.cordapp.CordappProvider
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.concurrent.transpose
|
||||
import net.corda.core.internal.toLedgerTransaction
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
@ -21,19 +15,16 @@ import net.corda.core.node.services.AttachmentStorage
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.serialization.SerializationFactory
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.node.VersionInfo
|
||||
import net.corda.node.internal.cordapp.JarScanningCordappLoader
|
||||
import net.corda.node.internal.cordapp.CordappProviderImpl
|
||||
import net.corda.node.internal.cordapp.JarScanningCordappLoader
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.core.DUMMY_BANK_A_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import net.corda.testing.core.TestIdentity
|
||||
import net.corda.testing.driver.DriverDSL
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.NodeHandle
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.internal.MockCordappConfigProvider
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
@ -59,7 +50,6 @@ class AttachmentLoadingTests {
|
||||
private val appContext get() = provider.getAppContext(cordapp)
|
||||
|
||||
private companion object {
|
||||
private val logger = contextLogger()
|
||||
val isolatedJAR = AttachmentLoadingTests::class.java.getResource("isolated.jar")!!
|
||||
const val ISOLATED_CONTRACT_ID = "net.corda.finance.contracts.isolated.AnotherDummyContract"
|
||||
|
||||
@ -70,12 +60,6 @@ class AttachmentLoadingTests {
|
||||
.asSubclass(FlowLogic::class.java)
|
||||
val DUMMY_BANK_A = TestIdentity(DUMMY_BANK_A_NAME, 40).party
|
||||
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
|
||||
private fun DriverDSL.createTwoNodes(): List<NodeHandle> {
|
||||
return listOf(
|
||||
startNode(providedName = bankAName),
|
||||
startNode(providedName = bankBName)
|
||||
).transpose().getOrThrow()
|
||||
}
|
||||
}
|
||||
|
||||
private val services = object : ServicesForResolution {
|
||||
|
@ -61,11 +61,13 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
||||
/**
|
||||
* Creates a CordappLoader from multiple directories.
|
||||
*
|
||||
* @param corDappDirectories Directories used to scan for CorDapp JARs.
|
||||
* @param cordappDirs Directories used to scan for CorDapp JARs.
|
||||
*/
|
||||
fun fromDirectories(corDappDirectories: Iterable<Path>, versionInfo: VersionInfo = VersionInfo.UNKNOWN, extraCordapps: List<CordappImpl> = emptyList()): JarScanningCordappLoader {
|
||||
logger.info("Looking for CorDapps in ${corDappDirectories.distinct().joinToString(", ", "[", "]")}")
|
||||
val paths = corDappDirectories.distinct().flatMap(this::jarUrlsInDirectory).map { it.restricted() }
|
||||
fun fromDirectories(cordappDirs: Collection<Path>,
|
||||
versionInfo: VersionInfo = VersionInfo.UNKNOWN,
|
||||
extraCordapps: List<CordappImpl> = emptyList()): JarScanningCordappLoader {
|
||||
logger.info("Looking for CorDapps in ${cordappDirs.distinct().joinToString(", ", "[", "]")}")
|
||||
val paths = cordappDirs.distinct().flatMap(this::jarUrlsInDirectory).map { it.restricted() }
|
||||
return JarScanningCordappLoader(paths, versionInfo, extraCordapps)
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ import net.corda.core.internal.cordapp.CordappImpl.Info.Companion.UNKNOWN_VALUE
|
||||
import java.util.jar.Attributes
|
||||
import java.util.jar.Manifest
|
||||
|
||||
fun createTestManifest(name: String, title: String, version: String, vendor: String): Manifest {
|
||||
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.
|
||||
@ -20,6 +20,7 @@ fun createTestManifest(name: String, title: String, version: String, vendor: Str
|
||||
manifest["Implementation-Title"] = title
|
||||
manifest["Implementation-Version"] = version
|
||||
manifest["Implementation-Vendor"] = vendor
|
||||
manifest["Target-Platform-Version"] = targetVersion.toString()
|
||||
|
||||
return manifest
|
||||
}
|
||||
|
@ -1 +0,0 @@
|
||||
key=value
|
@ -2,14 +2,12 @@ package net.corda.node.internal.cordapp
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.internal.packageName
|
||||
import net.corda.node.VersionInfo
|
||||
import net.corda.node.cordapp.CordappLoader
|
||||
import net.corda.testing.node.internal.cordappsForPackages
|
||||
import net.corda.testing.node.internal.getTimestampAsDirectoryName
|
||||
import net.corda.testing.node.internal.packageInDirectory
|
||||
import net.corda.testing.node.internal.TestCordappDirectories
|
||||
import net.corda.testing.node.internal.cordappForPackages
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Test
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
@InitiatingFlow
|
||||
@ -38,7 +36,6 @@ class DummyRPCFlow : FlowLogic<Unit>() {
|
||||
|
||||
class JarScanningCordappLoaderTest {
|
||||
private companion object {
|
||||
const val testScanPackage = "net.corda.node.internal.cordapp"
|
||||
const val isolatedContractId = "net.corda.finance.contracts.isolated.AnotherDummyContract"
|
||||
const val isolatedFlowName = "net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator"
|
||||
}
|
||||
@ -70,32 +67,18 @@ class JarScanningCordappLoaderTest {
|
||||
|
||||
@Test
|
||||
fun `flows are loaded by loader`() {
|
||||
val loader = cordappLoaderForPackages(listOf(testScanPackage))
|
||||
val dir = TestCordappDirectories.getJarDirectory(cordappForPackages(javaClass.packageName))
|
||||
val loader = JarScanningCordappLoader.fromDirectories(listOf(dir))
|
||||
|
||||
val actual = loader.cordapps.toTypedArray()
|
||||
// One cordapp from this source tree. In gradle it will also pick up the node jar.
|
||||
assertThat(actual.size == 0 || actual.size == 1).isTrue()
|
||||
assertThat(loader.cordapps).isNotEmpty
|
||||
|
||||
val actualCordapp = actual.single { !it.initiatedFlows.isEmpty() }
|
||||
val actualCordapp = loader.cordapps.single { !it.initiatedFlows.isEmpty() }
|
||||
assertThat(actualCordapp.initiatedFlows).first().hasSameClassAs(DummyFlow::class.java)
|
||||
assertThat(actualCordapp.rpcFlows).first().hasSameClassAs(DummyRPCFlow::class.java)
|
||||
assertThat(actualCordapp.schedulableFlows).first().hasSameClassAs(DummySchedulableFlow::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `duplicate packages are ignored`() {
|
||||
val loader = cordappLoaderForPackages(listOf(testScanPackage, testScanPackage))
|
||||
val cordapps = loader.cordapps.filter { LoaderTestFlow::class.java in it.initiatedFlows }
|
||||
assertThat(cordapps).hasSize(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `sub-packages are ignored`() {
|
||||
val loader = cordappLoaderForPackages(listOf("net.corda.core", testScanPackage))
|
||||
val cordapps = loader.cordapps.filter { LoaderTestFlow::class.java in it.initiatedFlows }
|
||||
assertThat(cordapps).hasSize(1)
|
||||
}
|
||||
|
||||
// This test exists because the appClassLoader is used by serialisation and we need to ensure it is the classloader
|
||||
// being used internally. Later iterations will use a classloader per cordapp and this test can be retired.
|
||||
@Test
|
||||
@ -159,16 +142,4 @@ class JarScanningCordappLoaderTest {
|
||||
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 2))
|
||||
assertThat(loader.cordapps).hasSize(1)
|
||||
}
|
||||
|
||||
private fun cordappLoaderForPackages(packages: Iterable<String>): CordappLoader {
|
||||
val cordapps = cordappsForPackages(packages)
|
||||
return testDirectory().let { directory ->
|
||||
cordapps.packageInDirectory(directory)
|
||||
JarScanningCordappLoader.fromDirectories(listOf(directory))
|
||||
}
|
||||
}
|
||||
|
||||
private fun testDirectory(): Path {
|
||||
return Paths.get("build", getTimestampAsDirectoryName())
|
||||
}
|
||||
}
|
||||
|
@ -15,11 +15,7 @@ import net.corda.node.services.statemachine.StaffedFlowHospital.MedicalRecord.Ke
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.core.singleIdentity
|
||||
import net.corda.testing.driver.TestCorDapp
|
||||
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.startFlow
|
||||
import net.corda.testing.node.internal.*
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.Test
|
||||
@ -38,8 +34,7 @@ class FinalityHandlerTest {
|
||||
// CorDapp. Bob's FinalityHandler will error when validating the tx.
|
||||
mockNet = InternalMockNetwork()
|
||||
|
||||
val assertCordapp = TestCorDapp.Factory.create("net.corda.finance.contracts.asset", "1.0").plusPackage("net.corda.finance.contracts.asset")
|
||||
val alice = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME, additionalCordapps = setOf(assertCordapp)))
|
||||
val alice = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME, additionalCordapps = setOf(FINANCE_CORDAPP)))
|
||||
|
||||
var bob = mockNet.createNode(InternalMockNodeParameters(legalName = BOB_NAME))
|
||||
|
||||
|
Reference in New Issue
Block a user