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:
Shams Asari 2018-10-15 10:11:18 +01:00 committed by GitHub
parent b769ad80bd
commit 2c9a942e1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 475 additions and 753 deletions

View File

@ -5822,8 +5822,6 @@ public interface net.corda.testing.driver.DriverDSL
@NotNull
public abstract net.corda.core.concurrent.CordaFuture<net.corda.testing.driver.NodeHandle> startNode(net.corda.testing.driver.NodeParameters, net.corda.core.identity.CordaX500Name, java.util.List<net.corda.testing.node.User>, net.corda.testing.driver.VerifierType, java.util.Map<String, ?>, Boolean, String)
@NotNull
public abstract net.corda.core.concurrent.CordaFuture<net.corda.testing.driver.NodeHandle> startNode(net.corda.testing.driver.NodeParameters, net.corda.core.identity.CordaX500Name, java.util.List<net.corda.testing.node.User>, net.corda.testing.driver.VerifierType, java.util.Map<String, ?>, Boolean, String, java.util.Set<? extends net.corda.testing.driver.TestCorDapp>, boolean)
@NotNull
public abstract net.corda.core.concurrent.CordaFuture<net.corda.testing.driver.WebserverHandle> startWebserver(net.corda.testing.driver.NodeHandle)
@NotNull
public abstract net.corda.core.concurrent.CordaFuture<net.corda.testing.driver.WebserverHandle> startWebserver(net.corda.testing.driver.NodeHandle, String)
@ -5832,10 +5830,7 @@ public final class net.corda.testing.driver.DriverParameters extends java.lang.O
public <init>()
public <init>(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map<String, String>, boolean, boolean, boolean, java.util.List<net.corda.testing.node.NotarySpec>, java.util.List<String>, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters)
public <init>(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map<String, String>, boolean, boolean, boolean, java.util.List<net.corda.testing.node.NotarySpec>, java.util.List<String>, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, java.util.Map<String, ?>, boolean, boolean)
public <init>(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map<String, String>, boolean, boolean, boolean, java.util.List<net.corda.testing.node.NotarySpec>, java.util.List<String>, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, java.util.Map<String, ?>, boolean, boolean, java.util.Set<? extends net.corda.testing.driver.TestCorDapp>)
public <init>(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map<String, String>, boolean, boolean, boolean, java.util.List<net.corda.testing.node.NotarySpec>, java.util.List<String>, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, java.util.Set<? extends net.corda.testing.driver.TestCorDapp>)
public <init>(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map<String, String>, boolean, boolean, boolean, java.util.List<net.corda.testing.node.NotarySpec>, java.util.List<String>, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, boolean, boolean)
public <init>(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map<String, String>, boolean, boolean, boolean, java.util.List<net.corda.testing.node.NotarySpec>, java.util.List<String>, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, boolean, boolean, java.util.Set<? extends net.corda.testing.driver.TestCorDapp>)
public final boolean component1()
@NotNull
public final java.util.List<String> component10()
@ -6028,75 +6023,6 @@ public static final class net.corda.testing.driver.PortAllocation$Incremental ex
public final java.util.concurrent.atomic.AtomicInteger getPortCounter()
public int nextPort()
##
@DoNotImplement
public interface net.corda.testing.driver.TestCorDapp
@NotNull
public abstract java.util.Set<Class<?>> getClasses()
@NotNull
public abstract String getName()
@NotNull
public abstract java.util.Set<java.net.URL> getResources()
@NotNull
public abstract String getTitle()
@NotNull
public abstract String getVendor()
@NotNull
public abstract String getVersion()
@NotNull
public abstract java.nio.file.Path packageAsJarInDirectory(java.nio.file.Path)
public abstract void packageAsJarWithPath(java.nio.file.Path)
##
public static final class net.corda.testing.driver.TestCorDapp$Factory extends java.lang.Object
public <init>()
@NotNull
public static final net.corda.testing.driver.TestCorDapp$Mutable create(String, String, String, String, java.util.Set<? extends Class<?>>, kotlin.jvm.functions.Function2<? super String, ? super java.net.URL, Boolean>)
public static final net.corda.testing.driver.TestCorDapp$Factory$Companion Companion
##
public static final class net.corda.testing.driver.TestCorDapp$Factory$Companion extends java.lang.Object
@NotNull
public final net.corda.testing.driver.TestCorDapp$Mutable create(String, String, String, String, java.util.Set<? extends Class<?>>, kotlin.jvm.functions.Function2<? super String, ? super java.net.URL, Boolean>)
##
@DoNotImplement
public static interface net.corda.testing.driver.TestCorDapp$Mutable extends net.corda.testing.driver.TestCorDapp
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable minus(Class<?>)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable minusPackage(Package)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable minusPackage(String)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable minusPackages(Package, Package...)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable minusPackages(String, String...)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable minusPackages(java.util.Set<String>)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable minusResource(String, java.net.URL)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable plus(Class<?>)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable plusPackage(Package)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable plusPackage(String)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable plusPackages(Package, Package...)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable plusPackages(String, String...)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable plusPackages(java.util.Set<String>)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable plusResource(String, java.net.URL)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable withClasses(java.util.Set<? extends Class<?>>)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable withName(String)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable withTitle(String)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable withVendor(String)
@NotNull
public abstract net.corda.testing.driver.TestCorDapp$Mutable withVersion(String)
##
public final class net.corda.testing.driver.VerifierType extends java.lang.Enum
protected <init>()
public static net.corda.testing.driver.VerifierType valueOf(String)
@ -6238,9 +6164,9 @@ public class net.corda.testing.node.MockNetwork extends java.lang.Object
@NotNull
public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>)
@NotNull
public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.List<String>)
public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.Collection<? extends net.corda.testing.node.TestCordapp>)
@NotNull
public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.Set<? extends net.corda.testing.driver.TestCorDapp>)
public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.List<String>)
@NotNull
public final net.corda.testing.node.StartedMockNode createNode(net.corda.testing.node.MockNodeParameters)
@NotNull
@ -6256,9 +6182,9 @@ public class net.corda.testing.node.MockNetwork extends java.lang.Object
@NotNull
public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>)
@NotNull
public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.List<String>)
public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.Collection<? extends net.corda.testing.node.TestCordapp>)
@NotNull
public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.Set<? extends net.corda.testing.driver.TestCorDapp>)
public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.List<String>)
@NotNull
public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.testing.node.MockNodeParameters)
@NotNull
@ -6339,8 +6265,7 @@ public final class net.corda.testing.node.MockNetworkParameters extends java.lan
public final class net.corda.testing.node.MockNodeParameters extends java.lang.Object
public <init>()
public <init>(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>)
public <init>(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.List<String>)
public <init>(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.Set<? extends net.corda.testing.driver.TestCorDapp>)
public <init>(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.Collection<? extends net.corda.testing.node.TestCordapp>)
@Nullable
public final Integer component1()
@Nullable
@ -6352,9 +6277,7 @@ public final class net.corda.testing.node.MockNodeParameters extends java.lang.O
@NotNull
public final net.corda.testing.node.MockNodeParameters copy(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>)
@NotNull
public final net.corda.testing.node.MockNodeParameters copy(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.List<String>)
@NotNull
public final net.corda.testing.node.MockNodeParameters copy(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.Set<? extends net.corda.testing.driver.TestCorDapp>)
public final net.corda.testing.node.MockNodeParameters copy(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.Collection<? extends net.corda.testing.node.TestCordapp>)
public boolean equals(Object)
@NotNull
public final kotlin.jvm.functions.Function1<net.corda.node.services.config.NodeConfiguration, Object> getConfigOverrides()

View File

@ -15,7 +15,6 @@ import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.node.VersionInfo
import net.corda.node.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.node.internal.cordapp.JarScanningCordappLoader
@ -25,16 +24,13 @@ 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.getTimestampAsDirectoryName
import net.corda.testing.node.internal.packageInDirectory
import net.corda.testing.services.MockAttachmentStorage
import org.assertj.core.api.Assertions.assertThat
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Rule
import org.junit.Test
import java.nio.file.Path
import java.nio.file.Paths
class AttachmentsClassLoaderStaticContractTests {
private companion object {
@ -101,22 +97,11 @@ class AttachmentsClassLoaderStaticContractTests {
@Test
fun `verify that contract DummyContract is in classPath`() {
val contractClass = Class.forName("net.corda.nodeapi.internal.AttachmentsClassLoaderStaticContractTests\$AttachmentDummyContract")
val contract = contractClass.newInstance() as Contract
assertNotNull(contract)
assertThat(contractClass.newInstance()).isInstanceOf(Contract::class.java)
}
private fun cordappLoaderForPackages(packages: Iterable<String>): CordappLoader {
val cordapps = cordappsForPackages(packages)
return testDirectory().let { directory ->
cordapps.packageInDirectory(directory)
JarScanningCordappLoader.fromDirectories(listOf(directory), VersionInfo.UNKNOWN)
}
private fun cordappLoaderForPackages(packages: Collection<String>): CordappLoader {
val dirs = cordappsForPackages(packages).map { TestCordappDirectories.getJarDirectory(it) }
return JarScanningCordappLoader.fromDirectories(dirs)
}
private fun testDirectory(): Path {
return Paths.get("build", getTimestampAsDirectoryName())
}
}
}

View File

@ -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()

View File

@ -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
)
}
}

View File

@ -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 {

View File

@ -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)
}

View File

@ -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
}

View File

@ -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())
}
}

View File

@ -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))

View File

@ -19,6 +19,7 @@ import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.driver.PortAllocation.Incremental
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 rx.Observable
@ -134,8 +135,8 @@ abstract class PortAllocation {
* in. If null the Driver-level value will be used.
* @property maximumHeapSize The maximum JVM heap size to use for the node.
* @property logLevel Logging level threshold.
* @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.
* @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(
@ -146,7 +147,7 @@ data class NodeParameters(
val startInSameProcess: Boolean? = null,
val maximumHeapSize: String = "512m",
val logLevel: String? = null,
val additionalCordapps: Set<TestCorDapp> = emptySet(),
val additionalCordapps: Collection<TestCordapp> = emptySet(),
val regenerateCordappsOnStart: Boolean = false
) {
/**
@ -226,8 +227,8 @@ data class NodeParameters(
* @param startInSameProcess Determines if the node should be started inside the same process the Driver is running
* in. If null the Driver-level value will be used.
* @param maximumHeapSize The maximum JVM heap size to use for the node.
* @param additionalCordapps Additional [TestCorDapp]s that this node will have available, in addition to the ones common to all nodes managed by the [DriverDSL].
* @param regenerateCordappsOnStart Whether existing [TestCorDapp]s unique to this node will be re-generated on start. Useful when stopping and restarting the same node.
* @param additionalCordapps Additional [TestCordapp]s that this node will have available, in addition to the ones common to all nodes managed by the [DriverDSL].
* @param regenerateCordappsOnStart Whether existing [TestCordapp]s unique to this node will be re-generated on start. Useful when stopping and restarting the same node.
*/
constructor(
providedName: CordaX500Name?,
@ -236,7 +237,7 @@ data class NodeParameters(
customOverrides: Map<String, Any?>,
startInSameProcess: Boolean?,
maximumHeapSize: String,
additionalCordapps: Set<TestCorDapp> = emptySet(),
additionalCordapps: Set<TestCordapp> = emptySet(),
regenerateCordappsOnStart: Boolean = false
) : this(
providedName,
@ -291,7 +292,7 @@ data class NodeParameters(
fun withStartInSameProcess(startInSameProcess: Boolean?): NodeParameters = copy(startInSameProcess = startInSameProcess)
fun withMaximumHeapSize(maximumHeapSize: String): NodeParameters = copy(maximumHeapSize = maximumHeapSize)
fun withLogLevel(logLevel: String?): NodeParameters = copy(logLevel = logLevel)
fun withAdditionalCordapps(additionalCordapps: Set<TestCorDapp>): NodeParameters = copy(additionalCordapps = additionalCordapps)
fun withAdditionalCordapps(additionalCordapps: Set<TestCordapp>): NodeParameters = copy(additionalCordapps = additionalCordapps)
fun withDeleteExistingCordappsDirectory(regenerateCordappsOnStart: Boolean): NodeParameters = copy(regenerateCordappsOnStart = regenerateCordappsOnStart)
}
@ -379,7 +380,7 @@ fun <A> driver(defaultParameters: DriverParameters = DriverParameters(), dsl: Dr
* @property inMemoryDB Whether to use in-memory H2 for new nodes rather then on-disk (the node starts quicker, however
* the data is not persisted between node restarts). Has no effect if node is configured
* in any way to use database other than H2.
* @property cordappsForAllNodes [TestCorDapp]s that will be added to each node started by the [DriverDSL].
* @property cordappsForAllNodes [TestCordapp]s that will be added to each node started by the [DriverDSL].
*/
@Suppress("unused")
data class DriverParameters(
@ -398,7 +399,7 @@ data class DriverParameters(
val notaryCustomOverrides: Map<String, Any?> = emptyMap(),
val initialiseSerialization: Boolean = true,
val inMemoryDB: Boolean = true,
val cordappsForAllNodes: Set<TestCorDapp>? = null
val cordappsForAllNodes: Collection<TestCordapp>? = null
) {
constructor(
isDebug: Boolean = false,
@ -480,7 +481,7 @@ data class DriverParameters(
extraCordappPackagesToScan: List<String>,
jmxPolicy: JmxPolicy,
networkParameters: NetworkParameters,
cordappsForAllNodes: Set<TestCorDapp>? = null
cordappsForAllNodes: Collection<TestCordapp>? = null
) : this(
isDebug,
driverDirectory,
@ -549,7 +550,7 @@ data class DriverParameters(
networkParameters: NetworkParameters,
initialiseSerialization: Boolean,
inMemoryDB: Boolean,
cordappsForAllNodes: Set<TestCorDapp>? = null
cordappsForAllNodes: Set<TestCordapp>? = null
) : this(
isDebug,
driverDirectory,
@ -584,7 +585,7 @@ data class DriverParameters(
fun withNetworkParameters(networkParameters: NetworkParameters): DriverParameters = copy(networkParameters = networkParameters)
fun withNotaryCustomOverrides(notaryCustomOverrides: Map<String, Any?>): DriverParameters = copy(notaryCustomOverrides = notaryCustomOverrides)
fun withInMemoryDB(inMemoryDB: Boolean): DriverParameters = copy(inMemoryDB = inMemoryDB)
fun withCordappsForAllNodes(cordappsForAllNodes: Set<TestCorDapp>?): DriverParameters = copy(cordappsForAllNodes = cordappsForAllNodes)
fun withCordappsForAllNodes(cordappsForAllNodes: Set<TestCordapp>?): DriverParameters = copy(cordappsForAllNodes = cordappsForAllNodes)
fun copy(
isDebug: Boolean,
@ -660,7 +661,7 @@ data class DriverParameters(
extraCordappPackagesToScan: List<String>,
jmxPolicy: JmxPolicy,
networkParameters: NetworkParameters,
cordappsForAllNodes: Set<TestCorDapp>?
cordappsForAllNodes: Set<TestCordapp>?
) = this.copy(
isDebug = isDebug,
driverDirectory = driverDirectory,
@ -693,7 +694,7 @@ data class DriverParameters(
jmxPolicy: JmxPolicy,
networkParameters: NetworkParameters,
initialiseSerialization: Boolean,
cordappsForAllNodes: Set<TestCorDapp>?
cordappsForAllNodes: Set<TestCordapp>?
) = this.copy(
isDebug = isDebug,
driverDirectory = driverDirectory,

View File

@ -6,6 +6,7 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.concurrent.map
import net.corda.node.internal.Node
import net.corda.testing.node.TestCordapp
import net.corda.testing.node.User
import net.corda.testing.node.NotarySpec
import java.nio.file.Path
@ -95,8 +96,8 @@ interface DriverDSL {
* @param maximumHeapSize The maximum JVM heap size to use for the node as a [String]. By default a number is interpreted
* as being in bytes. Append the letter 'k' or 'K' to the value to indicate Kilobytes, 'm' or 'M' to indicate
* megabytes, and 'g' or 'G' to indicate gigabytes. The default value is "512m" = 512 megabytes.
* @param additionalCordapps Additional [TestCorDapp]s that this node will have available, in addition to the ones common to all nodes managed by the [DriverDSL].
* @param regenerateCordappsOnStart Whether existing [TestCorDapp]s unique to this node will be re-generated on start. Useful when stopping and restarting the same node.
* @param additionalCordapps Additional [TestCordapp]s that this node will have available, in addition to the ones common to all nodes managed by the [DriverDSL].
* @param regenerateCordappsOnStart Whether existing [TestCordapp]s unique to this node will be re-generated on start. Useful when stopping and restarting the same node.
* @return A [CordaFuture] on the [NodeHandle] to the node. The future will complete when the node is available and
* it sees all previously started nodes, including the notaries.
*/
@ -108,7 +109,7 @@ interface DriverDSL {
customOverrides: Map<String, Any?> = defaultParameters.customOverrides,
startInSameProcess: Boolean? = defaultParameters.startInSameProcess,
maximumHeapSize: String = defaultParameters.maximumHeapSize,
additionalCordapps: Set<TestCorDapp> = defaultParameters.additionalCordapps,
additionalCordapps: Collection<TestCordapp> = defaultParameters.additionalCordapps,
regenerateCordappsOnStart: Boolean = defaultParameters.regenerateCordappsOnStart
): CordaFuture<NodeHandle>

View File

@ -1,85 +0,0 @@
package net.corda.testing.driver
import net.corda.core.DoNotImplement
import net.corda.testing.node.internal.MutableTestCorDapp
import java.net.URL
import java.nio.file.Path
/**
* Represents information about a CorDapp. Used to generate CorDapp JARs in tests.
*/
@DoNotImplement
interface TestCorDapp {
val name: String
val title: String
val version: String
val vendor: String
val classes: Set<Class<*>>
val resources: Set<URL>
fun packageAsJarInDirectory(parentDirectory: Path): Path
fun packageAsJarWithPath(jarFilePath: Path)
/**
* Responsible of creating [TestCorDapp]s.
*/
class Factory {
companion object {
/**
* Returns a builder-style [TestCorDapp] to easily generate different [TestCorDapp]s that have something in common.
*/
@JvmStatic
fun create(name: String, version: String, vendor: String = "R3", title: String = name, classes: Set<Class<*>> = emptySet(), willResourceBeAddedBeToCorDapp: (fullyQualifiedName: String, url: URL) -> Boolean = MutableTestCorDapp.Companion::filterTestCorDappClass): TestCorDapp.Mutable {
return MutableTestCorDapp(name, version, vendor, title, classes, willResourceBeAddedBeToCorDapp)
}
}
}
@DoNotImplement
interface Mutable : TestCorDapp {
fun withName(name: String): TestCorDapp.Mutable
fun withTitle(title: String): TestCorDapp.Mutable
fun withVersion(version: String): TestCorDapp.Mutable
fun withVendor(vendor: String): TestCorDapp.Mutable
fun withClasses(classes: Set<Class<*>>): TestCorDapp.Mutable
fun plusPackages(pckgs: Set<String>): TestCorDapp.Mutable
fun minusPackages(pckgs: Set<String>): TestCorDapp.Mutable
fun plusPackage(pckg: String): TestCorDapp.Mutable = plusPackages(setOf(pckg))
fun minusPackage(pckg: String): TestCorDapp.Mutable = minusPackages(setOf(pckg))
fun plusPackage(pckg: Package): TestCorDapp.Mutable = plusPackages(pckg.name)
fun minusPackage(pckg: Package): TestCorDapp.Mutable = minusPackages(pckg.name)
operator fun plus(clazz: Class<*>): TestCorDapp.Mutable = withClasses(classes + clazz)
operator fun minus(clazz: Class<*>): TestCorDapp.Mutable = withClasses(classes - clazz)
fun plusPackages(pckg: String, vararg pckgs: String): TestCorDapp.Mutable = plusPackages(setOf(pckg, *pckgs))
fun plusPackages(pckg: Package, vararg pckgs: Package): TestCorDapp.Mutable = minusPackages(setOf(pckg, *pckgs).map { it.name }.toSet())
fun minusPackages(pckg: String, vararg pckgs: String): TestCorDapp.Mutable = minusPackages(setOf(pckg, *pckgs))
fun minusPackages(pckg: Package, vararg pckgs: Package): TestCorDapp.Mutable = minusPackages(setOf(pckg, *pckgs).map { it.name }.toSet())
fun plusResource(fullyQualifiedName: String, url: URL): TestCorDapp.Mutable
fun minusResource(fullyQualifiedName: String, url: URL): TestCorDapp.Mutable
}
}

View File

@ -16,7 +16,6 @@ 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.driver.TestCorDapp
import net.corda.testing.node.internal.*
import rx.Observable
import java.math.BigInteger
@ -34,36 +33,29 @@ import java.util.concurrent.Future
* @property 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.
* @property configOverrides Add/override behaviour of the [NodeConfiguration] mock object.
* @property additionalCordapps [TestCorDapp]s that will be added to this node in addition to the ones shared by all nodes, which get specified at [MockNetwork] level.
* @property additionalCordapps [TestCordapp]s that will be added to this node in addition to the ones shared by all nodes, which get specified at [MockNetwork] level.
*/
@Suppress("unused")
data class MockNodeParameters constructor(
data class MockNodeParameters(
val forcedID: Int? = null,
val legalName: CordaX500Name? = null,
val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
val configOverrides: (NodeConfiguration) -> Any? = {},
val additionalCordapps: Set<TestCorDapp>) {
val additionalCordapps: Collection<TestCordapp> = emptyList()) {
@JvmOverloads
constructor(
forcedID: Int? = null,
legalName: CordaX500Name? = null,
entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
configOverrides: (NodeConfiguration) -> Any? = {},
extraCordappPackages: List<String> = emptyList()
) : this(forcedID, legalName, entropyRoot, configOverrides, additionalCordapps = cordappsForPackages(extraCordappPackages))
constructor(forcedID: Int? = null,
legalName: CordaX500Name? = null,
entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
configOverrides: (NodeConfiguration) -> Any? = {}
) : this(forcedID, legalName, entropyRoot, configOverrides, emptyList())
fun withForcedID(forcedID: Int?): MockNodeParameters = copy(forcedID = forcedID)
fun withLegalName(legalName: CordaX500Name?): MockNodeParameters = copy(legalName = legalName)
fun withEntropyRoot(entropyRoot: BigInteger): MockNodeParameters = copy(entropyRoot = entropyRoot)
fun withConfigOverrides(configOverrides: (NodeConfiguration) -> Any?): MockNodeParameters = copy(configOverrides = configOverrides)
fun withExtraCordappPackages(extraCordappPackages: List<String>): MockNodeParameters = copy(forcedID = forcedID, legalName = legalName, entropyRoot = entropyRoot, configOverrides = configOverrides, extraCordappPackages = extraCordappPackages)
fun withAdditionalCordapps(additionalCordapps: Set<TestCorDapp>): MockNodeParameters = copy(additionalCordapps = additionalCordapps)
fun withAdditionalCordapps(additionalCordapps: Collection<TestCordapp>): MockNodeParameters = copy(additionalCordapps = additionalCordapps)
fun copy(forcedID: Int?, legalName: CordaX500Name?, entropyRoot: BigInteger, configOverrides: (NodeConfiguration) -> Any?): MockNodeParameters {
return MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, additionalCordapps = emptySet())
}
fun copy(forcedID: Int?, legalName: CordaX500Name?, entropyRoot: BigInteger, configOverrides: (NodeConfiguration) -> Any?, extraCordappPackages: List<String> = emptyList()): MockNodeParameters {
return MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, extraCordappPackages)
return MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides)
}
}
@ -293,7 +285,7 @@ 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].
* @property cordappsForAllNodes [TestCordapp]s that will be added to each node started by the [MockNetwork].
*/
@Suppress("MemberVisibilityCanBePrivate", "CanBeParameter")
open class MockNetwork(
@ -304,7 +296,7 @@ open class MockNetwork(
val servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy,
val notarySpecs: List<MockNetworkNotarySpec> = defaultParameters.notarySpecs,
val networkParameters: NetworkParameters = defaultParameters.networkParameters,
val cordappsForAllNodes: Set<TestCorDapp> = cordappsForPackages(cordappPackages)) {
val cordappsForAllNodes: Collection<TestCordapp> = cordappsForPackages(cordappPackages)) {
@JvmOverloads
constructor(cordappPackages: List<String>, parameters: MockNetworkParameters = MockNetworkParameters()) : this(cordappPackages, defaultParameters = parameters)
@ -345,7 +337,9 @@ open class MockNetwork(
fun createPartyNode(legalName: CordaX500Name? = null): StartedMockNode = StartedMockNode.create(internalMockNetwork.createPartyNode(legalName))
/** Create a started node with the given parameters. **/
fun createNode(parameters: MockNodeParameters = MockNodeParameters()): StartedMockNode = StartedMockNode.create(internalMockNetwork.createNode(InternalMockNodeParameters(parameters)))
fun createNode(parameters: MockNodeParameters = MockNodeParameters()): StartedMockNode {
return StartedMockNode.create(internalMockNetwork.createNode(InternalMockNodeParameters(parameters)))
}
/**
* Create a started node with the given parameters.
@ -375,13 +369,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].
* @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: (NodeConfiguration) -> Any? = {},
additionalCordapps: Set<TestCorDapp>): StartedMockNode {
additionalCordapps: Collection<TestCordapp>): StartedMockNode {
val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, additionalCordapps)
return StartedMockNode.create(internalMockNetwork.createNode(InternalMockNodeParameters(parameters)))
}
@ -417,13 +411,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].
* @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 createUnstartedNode(legalName: CordaX500Name? = null,
forcedID: Int? = null,
entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
configOverrides: (NodeConfiguration) -> Any? = {},
additionalCordapps: Set<TestCorDapp>): UnstartedMockNode {
additionalCordapps: Collection<TestCordapp>): UnstartedMockNode {
val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, additionalCordapps)
return UnstartedMockNode.create(internalMockNetwork.createUnstartedNode(InternalMockNodeParameters(parameters)))
}

View File

@ -34,10 +34,7 @@ import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.DEV_ROOT_CA
import net.corda.testing.internal.MockCordappProvider
import net.corda.testing.node.internal.MockKeyManagementService
import net.corda.testing.node.internal.MockTransactionStorage
import net.corda.testing.node.internal.TestCordappDirectories
import net.corda.testing.node.internal.getCallerPackage
import net.corda.testing.node.internal.*
import net.corda.testing.services.MockAttachmentStorage
import java.security.KeyPair
import java.sql.Connection
@ -70,8 +67,7 @@ open class MockServices private constructor(
companion object {
private fun cordappLoaderForPackages(packages: Iterable<String>, versionInfo: VersionInfo = VersionInfo.UNKNOWN): CordappLoader {
val cordappPaths = TestCordappDirectories.forPackages(packages)
val cordappPaths = cordappsForPackages(packages).map { TestCordappDirectories.getJarDirectory(it) }
return JarScanningCordappLoader.fromDirectories(cordappPaths, versionInfo)
}

View File

@ -0,0 +1,69 @@
package net.corda.testing.node
import net.corda.core.DoNotImplement
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.testing.node.internal.TestCordappImpl
import net.corda.testing.node.internal.simplifyScanPackages
/**
* Represents information about a CorDapp. Used to generate CorDapp JARs in tests.
*/
@DoNotImplement
interface TestCordapp {
/** Returns the name, defaults to "test-cordapp" if not specified. */
val name: 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 "Corda" 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 set of package names scanned for this test CorDapp. */
val packages: Set<String>
/** 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
class Factory {
companion object {
@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 specified with the wither methods.
*/
@JvmStatic
fun fromPackages(packageNames: Collection<String>): TestCordapp {
return TestCordappImpl(
name = "test-cordapp",
version = "1.0",
vendor = "Corda",
title = "test-title",
targetVersion = PLATFORM_VERSION,
packages = simplifyScanPackages(packageNames),
classes = emptySet()
)
}
}
}
}

View File

@ -28,7 +28,6 @@ import net.corda.node.services.Permissions
import net.corda.node.services.config.*
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
import net.corda.node.utilities.registration.NodeRegistrationHelper
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.addShutdownHook
@ -38,6 +37,7 @@ import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier
import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
import net.corda.testing.node.TestCordapp
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.DUMMY_BANK_A_NAME
@ -91,7 +91,7 @@ class DriverDSLImpl(
val networkParameters: NetworkParameters,
val notaryCustomOverrides: Map<String, Any?>,
val inMemoryDB: Boolean,
val cordappsForAllNodes: Set<TestCorDapp>
val cordappsForAllNodes: Collection<TestCordapp>
) : InternalDriverDSL {
private var _executorService: ScheduledExecutorService? = null
@ -184,7 +184,25 @@ class DriverDSLImpl(
}
}
override fun startNode(defaultParameters: NodeParameters, providedName: CordaX500Name?, rpcUsers: List<User>, verifierType: VerifierType, customOverrides: Map<String, Any?>, startInSameProcess: Boolean?, maximumHeapSize: String) = startNode(defaultParameters, providedName, rpcUsers, verifierType, customOverrides, startInSameProcess, maximumHeapSize, defaultParameters.additionalCordapps, defaultParameters.regenerateCordappsOnStart)
override fun startNode(defaultParameters: NodeParameters,
providedName: CordaX500Name?,
rpcUsers: List<User>,
verifierType: VerifierType,
customOverrides: Map<String, Any?>,
startInSameProcess: Boolean?,
maximumHeapSize: String): CordaFuture<NodeHandle> {
return startNode(
defaultParameters,
providedName,
rpcUsers,
verifierType,
customOverrides,
startInSameProcess,
maximumHeapSize,
defaultParameters.additionalCordapps,
defaultParameters.regenerateCordappsOnStart
)
}
override fun startNode(
defaultParameters: NodeParameters,
@ -194,7 +212,7 @@ class DriverDSLImpl(
customOverrides: Map<String, Any?>,
startInSameProcess: Boolean?,
maximumHeapSize: String,
additionalCordapps: Set<TestCorDapp>,
additionalCordapps: Collection<TestCordapp>,
regenerateCordappsOnStart: Boolean
): CordaFuture<NodeHandle> {
val p2pAddress = portAllocation.nextHostAndPort()
@ -225,7 +243,7 @@ class DriverDSLImpl(
startInSameProcess: Boolean? = null,
maximumHeapSize: String = "512m",
p2pAddress: NetworkHostAndPort = portAllocation.nextHostAndPort(),
additionalCordapps: Set<TestCorDapp> = emptySet(),
additionalCordapps: Collection<TestCordapp> = emptySet(),
regenerateCordappsOnStart: Boolean = false): CordaFuture<NodeHandle> {
val rpcAddress = portAllocation.nextHostAndPort()
val rpcAdminAddress = portAllocation.nextHostAndPort()
@ -535,16 +553,12 @@ class DriverDSLImpl(
}
}
private val sharedCordappsDirectories: Iterable<Path> by lazy {
TestCordappDirectories.cached(cordappsForAllNodes)
}
private fun startNodeInternal(specifiedConfig: NodeConfig,
webAddress: NetworkHostAndPort,
startInProcess: Boolean?,
maximumHeapSize: String,
localNetworkMap: LocalNetworkMap?,
additionalCordapps: Set<TestCorDapp>,
additionalCordapps: Collection<TestCordapp>,
regenerateCordappsOnStart: Boolean = false): CordaFuture<NodeHandle> {
val visibilityHandle = networkVisibilityController.register(specifiedConfig.corda.myLegalName)
val baseDirectory = specifiedConfig.corda.baseDirectory.createDirectories()
@ -558,11 +572,17 @@ class DriverDSLImpl(
val useHTTPS = specifiedConfig.typesafe.run { hasPath("useHTTPS") && getBoolean("useHTTPS") }
val existingCorDappDirectoriesOption = if (regenerateCordappsOnStart) emptyList<String>() else if (specifiedConfig.typesafe.hasPath(NodeConfiguration.cordappDirectoriesKey)) specifiedConfig.typesafe.getStringList(NodeConfiguration.cordappDirectoriesKey) else emptyList()
val existingCorDappDirectoriesOption = if (regenerateCordappsOnStart) {
emptyList()
} else if (specifiedConfig.typesafe.hasPath(NodeConfiguration.cordappDirectoriesKey)) {
specifiedConfig.typesafe.getStringList(NodeConfiguration.cordappDirectoriesKey)
} else {
emptyList()
}
val cordappDirectories = existingCorDappDirectoriesOption + sharedCordappsDirectories.map { it.toString() } + TestCordappDirectories.cached(additionalCordapps, regenerateCordappsOnStart).map { it.toString() }
val cordappDirectories = existingCorDappDirectoriesOption + (cordappsForAllNodes + additionalCordapps).map { TestCordappDirectories.getJarDirectory(it).toString() }
val config = NodeConfig(specifiedConfig.typesafe.withValue(NodeConfiguration.cordappDirectoriesKey, ConfigValueFactory.fromIterable(cordappDirectories)))
val config = NodeConfig(specifiedConfig.typesafe.withValue(NodeConfiguration.cordappDirectoriesKey, ConfigValueFactory.fromIterable(cordappDirectories.toSet())))
if (startInProcess ?: startNodesInProcess) {
val nodeAndThreadFuture = startInProcessNode(executorService, config)
@ -687,8 +707,13 @@ class DriverDSLImpl(
private fun <A> oneOf(array: Array<A>) = array[Random().nextInt(array.size)]
fun cordappsInCurrentAndAdditionalPackages(packagesToScan: Iterable<String> = emptySet()): Set<TestCorDapp> = cordappsForPackages(getCallerPackage() + packagesToScan)
fun cordappsInCurrentAndAdditionalPackages(firstPackage: String, vararg otherPackages: String): Set<TestCorDapp> = cordappsInCurrentAndAdditionalPackages(otherPackages.toList() + firstPackage)
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,
@ -1085,7 +1110,7 @@ fun <A> internalDriver(
compatibilityZone: CompatibilityZoneParams? = null,
notaryCustomOverrides: Map<String, Any?> = DriverParameters().notaryCustomOverrides,
inMemoryDB: Boolean = DriverParameters().inMemoryDB,
cordappsForAllNodes: Set<TestCorDapp> = DriverParameters().cordappsForAllNodes(),
cordappsForAllNodes: Collection<TestCordapp> = DriverParameters().cordappsForAllNodes(),
dsl: DriverDSLImpl.() -> A
): A {
return genericDriver(
@ -1125,7 +1150,7 @@ private fun Config.toNodeOnly(): Config {
return if (hasPath("webAddress")) withoutPath("webAddress").withoutPath("useHTTPS") else this
}
internal fun DriverParameters.cordappsForAllNodes(): Set<TestCorDapp> = cordappsForAllNodes
internal fun DriverParameters.cordappsForAllNodes(): Collection<TestCordapp> = cordappsForAllNodes
?: cordappsInCurrentAndAdditionalPackages(extraCordappPackagesToScan)
fun DriverDSL.startNode(providedName: CordaX500Name, devMode: Boolean, parameters: NodeParameters = NodeParameters()): CordaFuture<NodeHandle> {

View File

@ -51,8 +51,8 @@ import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.node.TestCordapp
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.driver.TestCorDapp
import net.corda.testing.internal.rigorousMock
import net.corda.testing.internal.setGlobalSerialization
import net.corda.testing.internal.stubs.CertificateStoreStubs
@ -89,7 +89,7 @@ data class InternalMockNodeParameters(
val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
val configOverrides: (NodeConfiguration) -> Any? = {},
val version: VersionInfo = MOCK_VERSION_INFO,
val additionalCordapps: Set<TestCorDapp>? = null) {
val additionalCordapps: Collection<TestCordapp>? = null) {
constructor(mockNodeParameters: MockNodeParameters) : this(
mockNodeParameters.forcedID,
mockNodeParameters.legalName,
@ -148,7 +148,7 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
val testDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()),
val networkParameters: NetworkParameters = testNetworkParameters(),
val defaultFactory: (MockNodeArgs) -> MockNode = { args -> MockNode(args) },
val cordappsForAllNodes: Set<TestCorDapp> = emptySet(),
val cordappsForAllNodes: Collection<TestCordapp> = emptySet(),
val autoVisibleNodes: Boolean = true) : AutoCloseable {
init {
// Apache SSHD for whatever reason registers a SFTP FileSystemProvider - which gets loaded by JimFS.
@ -174,10 +174,6 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
}
private val sharedUserCount = AtomicInteger(0)
private val sharedCorDappsDirectories: Iterable<Path> by lazy {
TestCordappDirectories.cached(cordappsForAllNodes)
}
/** A read only view of the current set of nodes. */
val nodes: List<MockNode> get() = _nodes
@ -453,8 +449,8 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
parameters.configOverrides(it)
}
val cordapps: Set<TestCorDapp> = parameters.additionalCordapps ?: emptySet()
val cordappDirectories = sharedCorDappsDirectories + TestCordappDirectories.cached(cordapps)
val cordapps = (parameters.additionalCordapps ?: emptySet()) + cordappsForAllNodes
val cordappDirectories = cordapps.map { TestCordappDirectories.getJarDirectory(it) }.distinct()
doReturn(cordappDirectories).whenever(config).cordappDirectories
val node = nodeFactory(MockNodeArgs(config, this, id, parameters.entropyRoot, parameters.version))

View File

@ -1,73 +0,0 @@
package net.corda.testing.node.internal
import net.corda.core.internal.div
import net.corda.testing.driver.TestCorDapp
import java.io.File
import java.net.URL
import java.nio.file.Path
internal class MutableTestCorDapp private constructor(override val name: String, override val version: String, override val vendor: String, override val title: String, private val willResourceBeAddedToCorDapp: (String, URL) -> Boolean, private val jarEntries: Set<JarEntryInfo>) : TestCorDapp.Mutable {
constructor(name: String, version: String, vendor: String, title: String, classes: Set<Class<*>>, willResourceBeAddedToCorDapp: (String, URL) -> Boolean) : this(name, version, vendor, title, willResourceBeAddedToCorDapp, jarEntriesFromClasses(classes))
companion object {
private const val jarExtension = ".jar"
private const val whitespace = " "
private const val whitespaceReplacement = "_"
private val productionPathSegments = setOf<(String) -> String>({ "out${File.separator}production${File.separator}classes" }, { fullyQualifiedName -> "main${File.separator}${fullyQualifiedName.packageToJarPath()}" })
private val excludedCordaPackages = setOf("net.corda.core", "net.corda.node")
fun filterTestCorDappClass(fullyQualifiedName: String, url: URL): Boolean {
return isTestResource(fullyQualifiedName, url) || !isInExcludedCordaPackage(fullyQualifiedName)
}
private fun isTestResource(fullyQualifiedName: String, url: URL): Boolean {
return productionPathSegments.map { it.invoke(fullyQualifiedName) }.none { url.toString().contains(it) }
}
private fun isInExcludedCordaPackage(packageName: String): Boolean {
return excludedCordaPackages.any { packageName.startsWith(it) }
}
private fun jarEntriesFromClasses(classes: Set<Class<*>>): Set<JarEntryInfo> {
val illegal = classes.filter { it.protectionDomain?.codeSource?.location == null }
if (illegal.isNotEmpty()) {
throw IllegalArgumentException("Some classes do not have a location, typically because they are part of Java or Kotlin. Offending types were: ${illegal.joinToString(", ", "[", "]") { it.simpleName }}")
}
return classes.map(Class<*>::jarEntryInfo).toSet()
}
}
override val classes: Set<Class<*>> = jarEntries.filterIsInstance(JarEntryInfo.ClassJarEntryInfo::class.java).map(JarEntryInfo.ClassJarEntryInfo::clazz).toSet()
override val resources: Set<URL> = jarEntries.map(JarEntryInfo::url).toSet()
override fun withName(name: String) = MutableTestCorDapp(name, version, vendor, title, classes, willResourceBeAddedToCorDapp)
override fun withTitle(title: String) = MutableTestCorDapp(name, version, vendor, title, classes, willResourceBeAddedToCorDapp)
override fun withVersion(version: String) = MutableTestCorDapp(name, version, vendor, title, classes, willResourceBeAddedToCorDapp)
override fun withVendor(vendor: String) = MutableTestCorDapp(name, version, vendor, title, classes, willResourceBeAddedToCorDapp)
override fun withClasses(classes: Set<Class<*>>) = MutableTestCorDapp(name, version, vendor, title, classes, willResourceBeAddedToCorDapp)
override fun plusPackages(pckgs: Set<String>) = withClasses(pckgs.map { allClassesForPackage(it) }.fold(classes) { all, packageClasses -> all + packageClasses })
override fun minusPackages(pckgs: Set<String>) = withClasses(pckgs.map { allClassesForPackage(it) }.fold(classes) { all, packageClasses -> all - packageClasses })
override fun plusResource(fullyQualifiedName: String, url: URL): TestCorDapp.Mutable = MutableTestCorDapp(name, version, vendor, title, willResourceBeAddedToCorDapp, jarEntries + JarEntryInfo.ResourceJarEntryInfo(fullyQualifiedName, url))
override fun minusResource(fullyQualifiedName: String, url: URL): TestCorDapp.Mutable = MutableTestCorDapp(name, version, vendor, title, willResourceBeAddedToCorDapp, jarEntries - JarEntryInfo.ResourceJarEntryInfo(fullyQualifiedName, url))
override fun packageAsJarWithPath(jarFilePath: Path) = jarEntries.packageToCorDapp(jarFilePath, name, version, vendor, title, willResourceBeAddedToCorDapp)
override fun packageAsJarInDirectory(parentDirectory: Path): Path = (parentDirectory / defaultJarName()).also { packageAsJarWithPath(it) }
private fun defaultJarName(): String = "${name}_$version$jarExtension".replace(whitespace, whitespaceReplacement)
}

View File

@ -9,7 +9,6 @@ import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.node.NodeInfo
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor
import net.corda.node.VersionInfo
import net.corda.node.internal.Node
import net.corda.node.internal.NodeWithInfo
@ -108,9 +107,9 @@ abstract class NodeBasedTest(private val cordappPackages: List<String> = emptyLi
val existingCorDappDirectoriesOption = if (config.hasPath(NodeConfiguration.cordappDirectoriesKey)) config.getStringList(NodeConfiguration.cordappDirectoriesKey) else emptyList()
val cordappDirectories = existingCorDappDirectoriesOption + TestCordappDirectories.cached(cordapps).map { it.toString() }
val cordappDirectories = existingCorDappDirectoriesOption + cordapps.map { TestCordappDirectories.getJarDirectory(it).toString() }
val specificConfig = config.withValue(NodeConfiguration.cordappDirectoriesKey, ConfigValueFactory.fromIterable(cordappDirectories))
val specificConfig = config.withValue(NodeConfiguration.cordappDirectoriesKey, ConfigValueFactory.fromIterable(cordappDirectories.toSet()))
val parsedConfig = specificConfig.parseAsNodeConfiguration().also { nodeConfiguration ->
val errors = nodeConfiguration.validate()

View File

@ -24,11 +24,11 @@ import net.corda.node.services.messaging.RPCServerConfiguration
import net.corda.nodeapi.RPCApi
import net.corda.nodeapi.internal.ArtemisTcpTransport
import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT
import net.corda.testing.node.TestCordapp
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.MAX_MESSAGE_SIZE
import net.corda.testing.driver.JmxPolicy
import net.corda.testing.driver.PortAllocation
import net.corda.testing.driver.TestCorDapp
import net.corda.testing.node.NotarySpec
import net.corda.testing.node.User
import net.corda.testing.node.internal.DriverDSLImpl.Companion.cordappsInCurrentAndAdditionalPackages
@ -118,7 +118,7 @@ fun <A> rpcDriver(
networkParameters: NetworkParameters = testNetworkParameters(),
notaryCustomOverrides: Map<String, Any?> = emptyMap(),
inMemoryDB: Boolean = true,
cordappsForAllNodes: Set<TestCorDapp> = cordappsInCurrentAndAdditionalPackages(),
cordappsForAllNodes: Collection<TestCordapp> = cordappsInCurrentAndAdditionalPackages(),
dsl: RPCDriverDSL.() -> A
): A {
return genericDriver(

View File

@ -1,70 +1,46 @@
package net.corda.testing.node.internal
import net.corda.core.crypto.sha256
import net.corda.core.internal.createDirectories
import net.corda.core.internal.deleteRecursively
import net.corda.core.internal.div
import net.corda.core.internal.exists
import net.corda.core.utilities.debug
import net.corda.core.utilities.loggerFor
import net.corda.testing.driver.TestCorDapp
import net.corda.testing.node.TestCordapp
import java.nio.file.Path
import java.nio.file.Paths
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentMap
internal object TestCordappDirectories {
object TestCordappDirectories {
private val logger = loggerFor<TestCordappDirectories>()
private const val whitespace = " "
private const val whitespaceReplacement = "_"
private val whitespace = "\\s".toRegex()
private val cordappsCache: ConcurrentMap<List<String>, Path> = ConcurrentHashMap<List<String>, Path>()
private val testCordappsCache = ConcurrentHashMap<TestCordappImpl, Path>()
internal fun cached(cordapps: Iterable<TestCorDapp>, replaceExistingOnes: Boolean = false, cordappsDirectory: Path = defaultCordappsDirectory): Iterable<Path> {
cordappsDirectory.toFile().deleteOnExit()
return cordapps.map { cached(it, replaceExistingOnes, cordappsDirectory) }
}
internal fun cached(cordapp: TestCorDapp, replaceExistingOnes: Boolean = false, cordappsDirectory: Path = defaultCordappsDirectory): Path {
cordappsDirectory.toFile().deleteOnExit()
val cacheKey = cordapp.resources.map { it.toExternalForm() }.sorted()
return if (replaceExistingOnes) {
cordappsCache.remove(cacheKey)
cordappsCache.getOrPut(cacheKey) {
val cordappDirectory = (cordappsDirectory / "${cordapp.name}_${cordapp.version}".replace(whitespace, whitespaceReplacement)).toAbsolutePath()
cordappDirectory.createDirectories()
cordapp.packageAsJarInDirectory(cordappDirectory)
cordappDirectory
}
} else {
cordappsCache.getOrPut(cacheKey) {
val cordappDirectory = (cordappsDirectory / "${cordapp.name}_${cordapp.version}".replace(whitespace, whitespaceReplacement)).toAbsolutePath()
cordappDirectory.createDirectories()
cordapp.packageAsJarInDirectory(cordappDirectory)
cordappDirectory
fun getJarDirectory(cordapp: TestCordapp, cordappsDirectory: Path = defaultCordappsDirectory): Path {
cordapp as TestCordappImpl
return testCordappsCache.computeIfAbsent(cordapp) {
val cordappDir = (cordappsDirectory / UUID.randomUUID().toString()).createDirectories()
val uniqueScanString = if (cordapp.packages.size == 1 && cordapp.classes.isEmpty()) {
cordapp.packages.first()
} else {
"${cordapp.packages}${cordapp.classes.joinToString { it.name }}".toByteArray().sha256().toString()
}
val jarFileName = cordapp.run { "${name}_${vendor}_${title}_${version}_${targetVersion}_$uniqueScanString.jar".replace(whitespace, "-") }
val jarFile = cordappDir / jarFileName
cordapp.packageAsJar(jarFile)
logger.debug { "$cordapp packaged into $jarFile" }
cordappDir
}
}
internal fun forPackages(packages: Iterable<String>, replaceExistingOnes: Boolean = false, cordappsDirectory: Path = defaultCordappsDirectory): Iterable<Path> {
cordappsDirectory.toFile().deleteOnExit()
val cordapps = simplifyScanPackages(packages).distinct().fold(emptySet<TestCorDapp>()) { all, packageName -> all + testCorDapp(packageName) }
return cached(cordapps, replaceExistingOnes, cordappsDirectory)
}
private val defaultCordappsDirectory: Path by lazy {
val cordappsDirectory = (Paths.get("build") / "tmp" / getTimestampAsDirectoryName() / "generated-test-cordapps").toAbsolutePath()
logger.info("Initialising generated test CorDapps directory in $cordappsDirectory")
if (cordappsDirectory.exists()) {
cordappsDirectory.deleteRecursively()
}
cordappsDirectory.toFile().deleteOnExit()
cordappsDirectory.deleteRecursively()
cordappsDirectory.createDirectories()
cordappsDirectory
}
}
}

View File

@ -0,0 +1,26 @@
package net.corda.testing.node.internal
import net.corda.testing.node.TestCordapp
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 packages: Set<String>,
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)
fun withClasses(vararg classes: Class<*>): TestCordappImpl {
return copy(classes = classes.filter { clazz -> packages.none { clazz.name.startsWith("$it.") } }.toSet())
}
}

View File

@ -1,100 +1,34 @@
package net.corda.testing.node.internal
import io.github.classgraph.ClassGraph
import net.corda.core.internal.createDirectories
import net.corda.core.internal.deleteIfExists
import net.corda.core.internal.outputStream
import net.corda.node.internal.cordapp.createTestManifest
import net.corda.testing.driver.TestCorDapp
import org.apache.commons.io.IOUtils
import java.io.OutputStream
import java.net.URI
import java.net.URL
import net.corda.testing.node.TestCordapp
import java.nio.file.Path
import java.nio.file.attribute.FileTime
import java.time.Instant
import java.util.*
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import kotlin.reflect.KClass
/**
* Packages some [JarEntryInfo] into a CorDapp JAR with specified [path].
* @param path The path of the JAR.
* @param willResourceBeAddedBeToCorDapp A filter for the inclusion of [JarEntryInfo] in the JAR.
*/
internal fun Iterable<JarEntryInfo>.packageToCorDapp(path: Path, name: String, version: String, vendor: String, title: String = name, willResourceBeAddedBeToCorDapp: (String, URL) -> Boolean = { _, _ -> true }) {
@JvmField
val FINANCE_CORDAPP: TestCordappImpl = cordappForPackages("net.corda.finance")
var hasContent = false
try {
hasContent = packageToCorDapp(path.outputStream(), name, version, vendor, title, willResourceBeAddedBeToCorDapp)
} finally {
if (!hasContent) {
path.deleteIfExists()
}
}
/** Creates a [TestCordappImpl] for each package. */
fun cordappsForPackages(vararg packageNames: String): List<TestCordappImpl> = cordappsForPackages(packageNames.asList())
fun cordappsForPackages(packageNames: Iterable<String>): List<TestCordappImpl> {
return simplifyScanPackages(packageNames).map { cordappForPackages(it) }
}
/**
* Packages some [JarEntryInfo] into a CorDapp JAR using specified [outputStream].
* @param outputStream The [OutputStream] for the JAR.
* @param willResourceBeAddedBeToCorDapp A filter for the inclusion of [JarEntryInfo] in the JAR.
*/
internal fun Iterable<JarEntryInfo>.packageToCorDapp(outputStream: OutputStream, name: String, version: String, vendor: String, title: String = name, willResourceBeAddedBeToCorDapp: (String, URL) -> Boolean = { _, _ -> true }): Boolean {
val manifest = createTestManifest(name, title, version, vendor)
return JarOutputStream(outputStream, manifest).use { jos -> zip(jos, willResourceBeAddedBeToCorDapp) }
/** Creates a single [TestCordappImpl] containing all the given packges. */
fun cordappForPackages(vararg packageNames: String): TestCordappImpl {
return TestCordapp.Factory.fromPackages(*packageNames) as TestCordappImpl
}
/**
* Transforms a [Class] into a [JarEntryInfo].
*/
internal fun Class<*>.jarEntryInfo(): JarEntryInfo {
return JarEntryInfo.ClassJarEntryInfo(this)
}
/**
* Packages some [TestCorDapp]s under a root [directory], each with it's own JAR.
* @param directory The parent directory in which CorDapp JAR will be created.
*/
fun Iterable<TestCorDapp>.packageInDirectory(directory: Path) {
directory.createDirectories()
forEach { cordapp -> cordapp.packageAsJarInDirectory(directory) }
}
/**
* Returns all classes within the [targetPackage].
*/
fun allClassesForPackage(targetPackage: String): Set<Class<*>> {
return ClassGraph()
.whitelistPackages(targetPackage)
.enableAllInfo()
.scan()
.use { it.allClasses.loadClasses() }
.toSet()
}
/**
* Maps each package to a [TestCorDapp] with resources found in that package.
*/
fun cordappsForPackages(packages: Iterable<String>): Set<TestCorDapp> {
return simplifyScanPackages(packages).toSet().fold(emptySet()) { all, packageName -> all + testCorDapp(packageName) }
}
/**
* Maps each package to a [TestCorDapp] with resources found in that package.
*/
fun cordappsForPackages(firstPackage: String, vararg otherPackages: String): Set<TestCorDapp> {
return cordappsForPackages(setOf(*otherPackages) + firstPackage)
}
fun cordappForClasses(vararg classes: Class<*>): TestCordappImpl = cordappForPackages().withClasses(*classes)
fun getCallerClass(directCallerClass: KClass<*>): Class<*>? {
val stackTrace = Throwable().stackTrace
val index = stackTrace.indexOfLast { it.className == directCallerClass.java.name }
if (index == -1) return null
@ -105,112 +39,42 @@ fun getCallerClass(directCallerClass: KClass<*>): Class<*>? {
}
}
fun getCallerPackage(directCallerClass: KClass<*>): String? {
return getCallerClass(directCallerClass)?.`package`?.name
}
/**
* Returns a [TestCorDapp] containing resources found in [packageName].
*/
internal fun testCorDapp(packageName: String): TestCorDapp {
val uuid = UUID.randomUUID()
val name = "$packageName-$uuid"
val version = "$uuid"
return TestCorDapp.Factory.create(name, version).plusPackage(packageName)
}
fun getCallerPackage(directCallerClass: KClass<*>): String? = getCallerClass(directCallerClass)?.`package`?.name
/**
* Squashes child packages if the parent is present. Example: ["com.foo", "com.foo.bar"] into just ["com.foo"].
*/
fun simplifyScanPackages(scanPackages: Iterable<String>): List<String> {
return scanPackages.sorted().fold(emptyList()) { listSoFar, packageName ->
fun simplifyScanPackages(scanPackages: Iterable<String>): Set<String> {
return scanPackages.sorted().fold(emptySet()) { soFar, packageName ->
when {
listSoFar.isEmpty() -> listOf(packageName)
packageName.startsWith(listSoFar.last()) -> listSoFar
else -> listSoFar + packageName
soFar.isEmpty() -> setOf(packageName)
packageName.startsWith("${soFar.last()}.") -> soFar
else -> soFar + packageName
}
}
}
/**
* Transforms a class or package name into a path segment.
*/
internal fun String.packageToJarPath() = replace(".", "/")
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" }
private fun Iterable<JarEntryInfo>.zip(outputStream: ZipOutputStream, willResourceBeAddedBeToCorDapp: (String, URL) -> Boolean): Boolean {
val scanResult = ClassGraph()
.whitelistPackages(*packages.toTypedArray())
.whitelistClasses(*classes.map { it.name }.toTypedArray())
.scan()
val entries = filter { (fullyQualifiedName, url) -> willResourceBeAddedBeToCorDapp(fullyQualifiedName, url) }
if (entries.isNotEmpty()) {
zip(outputStream, entries)
}
return entries.isNotEmpty()
}
private fun zip(outputStream: ZipOutputStream, allInfo: Iterable<JarEntryInfo>) {
val time = FileTime.from(Instant.EPOCH)
val classLoader = Thread.currentThread().contextClassLoader
allInfo.distinctBy { it.url }.sortedBy { it.url.toExternalForm() }.forEach { info ->
try {
val entry = ZipEntry(info.entryName).setCreationTime(time).setLastAccessTime(time).setLastModifiedTime(time)
outputStream.putNextEntry(entry)
classLoader.getResourceAsStream(info.entryName).use {
IOUtils.copy(it, outputStream)
scanResult.use {
val manifest = createTestManifest(name, title, version, vendor, targetVersion)
JarOutputStream(file.outputStream(), manifest).use { jos ->
val time = FileTime.from(Instant.now())
// 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()
}
} finally {
outputStream.closeEntry()
}
}
}
/**
* Represents a single resource to be added to a CorDapp JAR.
*/
internal sealed class JarEntryInfo(val fullyQualifiedName: String, val url: URL) {
abstract val entryName: String
/**
* Represents a class to be added to a CorDapp JAR.
*/
class ClassJarEntryInfo(val clazz: Class<*>) : JarEntryInfo(clazz.name, clazz.classFileURL()) {
override val entryName = "${fullyQualifiedName.packageToJarPath()}$fileExtensionSeparator$classFileExtension"
}
/**
* Represents a resource file to be added to a CorDapp JAR.
*/
class ResourceJarEntryInfo(fullyQualifiedName: String, url: URL) : JarEntryInfo(fullyQualifiedName, url) {
override val entryName: String
get() {
val extensionIndex = fullyQualifiedName.lastIndexOf(fileExtensionSeparator)
return "${fullyQualifiedName.substring(0 until extensionIndex).packageToJarPath()}${fullyQualifiedName.substring(extensionIndex)}"
}
}
operator fun component1(): String = fullyQualifiedName
operator fun component2(): URL = url
private companion object {
private const val classFileExtension = "class"
private const val fileExtensionSeparator = "."
private const val whitespace = " "
private const val whitespaceReplacement = "%20"
private fun Class<*>.classFileURL(): URL {
require(protectionDomain?.codeSource?.location != null) { "Invalid class $name for test CorDapp. Classes without protection domain cannot be referenced. This typically happens for Java / Kotlin types." }
return URI.create("${protectionDomain.codeSource.location}/${name.packageToJarPath()}$fileExtensionSeparator$classFileExtension".escaped()).toURL()
}
private fun String.escaped(): String = this.replace(whitespace, whitespaceReplacement)
}
}

View File

@ -0,0 +1,93 @@
package net.corda.testing.node.internal
import net.corda.core.internal.inputStream
import net.corda.node.internal.cordapp.get
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()
assertThat(simplifyScanPackages(listOf("com.foo.bar"))).containsExactlyInAnyOrder("com.foo.bar")
assertThat(simplifyScanPackages(listOf("com.foo", "com.foo"))).containsExactlyInAnyOrder("com.foo")
assertThat(simplifyScanPackages(listOf("com.foo", "com.bar"))).containsExactlyInAnyOrder("com.foo", "com.bar")
assertThat(simplifyScanPackages(listOf("com.foo", "com.foo.bar"))).containsExactlyInAnyOrder("com.foo")
assertThat(simplifyScanPackages(listOf("com.foo.bar", "com.foo"))).containsExactlyInAnyOrder("com.foo")
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["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
}
}