Minor: reduce the size of the MockNetwork API a bit, add some better documentation

This commit is contained in:
Mike Hearn 2018-02-16 17:21:23 +01:00
parent 4bc3f9ffa8
commit c0448a0e3c
16 changed files with 71 additions and 70 deletions

View File

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

View File

@ -3,7 +3,6 @@ package net.corda.confidential
import net.corda.core.identity.*
import net.corda.core.utilities.getOrThrow
import net.corda.testing.core.*
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.internal.InternalMockNetwork
import org.junit.Before
import net.corda.testing.node.startFlow

View File

@ -13,7 +13,6 @@ import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNodeParameters
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.startFlow

View File

@ -18,7 +18,6 @@ import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.nodeapi.internal.persistence.currentDBSession
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNodeParameters
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.internal.InternalMockNetwork

View File

@ -103,7 +103,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
// We run this in parallel threads to help catch any race conditions that may exist. The other tests
// we run in the unit test thread exclusively to speed things up, ensure deterministic results and
// allow interruption half way through.
mockNet = InternalMockNetwork(threadPerNode = true, cordappPackages = cordappPackages)
mockNet = InternalMockNetwork(cordappPackages = cordappPackages, threadPerNode = true)
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) {
val notaryNode = mockNet.defaultNotaryNode
@ -155,7 +155,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
@Test(expected = InsufficientBalanceException::class)
fun `trade cash for commercial paper fails using soft locking`() {
mockNet = InternalMockNetwork(threadPerNode = true, cordappPackages = cordappPackages)
mockNet = InternalMockNetwork(cordappPackages = cordappPackages, threadPerNode = true)
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) {
val notaryNode = mockNet.defaultNotaryNode

View File

@ -47,7 +47,7 @@ class ScheduledFlowsDrainingModeTest {
@Before
fun setup() {
mockNet = InternalMockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.testing.contracts"))
mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts"), threadPerNode = true)
aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME))
bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME))
notary = mockNet.defaultNotaryIdentity

View File

@ -103,7 +103,7 @@ class ScheduledFlowTests {
@Before
fun setup() {
mockNet = InternalMockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.testing.contracts"))
mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts"), threadPerNode = true)
aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME))
bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME))
notary = mockNet.defaultNotaryIdentity

View File

@ -77,8 +77,8 @@ class FlowFrameworkTests {
@Before
fun start() {
mockNet = InternalMockNetwork(
servicePeerAllocationStrategy = RoundRobin(),
cordappPackages = listOf("net.corda.finance.contracts", "net.corda.testing.contracts")
cordappPackages = listOf("net.corda.finance.contracts", "net.corda.testing.contracts"),
servicePeerAllocationStrategy = RoundRobin()
)
aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME))
bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME))

View File

@ -14,8 +14,8 @@ import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.dummyCommand
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNodeParameters
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.startFlow
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
@ -25,7 +25,7 @@ import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class MaxTransactionSizeTests {
private lateinit var mockNet: MockNetwork
private lateinit var mockNet: InternalMockNetwork
private lateinit var notaryServices: StartedNodeServices
private lateinit var aliceServices: StartedNodeServices
private lateinit var notary: Party
@ -34,7 +34,7 @@ class MaxTransactionSizeTests {
@Before
fun setup() {
mockNet = MockNetwork(listOf("net.corda.testing.contracts", "net.corda.node.services.transactions"), maxTransactionSize = 3_000_000)
mockNet = InternalMockNetwork(listOf("net.corda.testing.contracts", "net.corda.node.services.transactions"), maxTransactionSize = 3_000_000)
val aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME))
val bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME))
notaryServices = mockNet.defaultNotaryNode.services

View File

@ -27,7 +27,6 @@ import net.corda.node.internal.InitiatedFlowFactory
import net.corda.node.services.api.VaultServiceInternal
import net.corda.nodeapi.internal.persistence.HibernateConfiguration
import net.corda.testing.core.chooseIdentity
import net.corda.testing.node.MockNetwork
import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockNodeParameters
import net.corda.testing.node.internal.InternalMockNetwork

View File

@ -71,9 +71,9 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean,
}
val mockNet = InternalMockNetwork(
cordappPackages = listOf("net.corda.finance.contract", "net.corda.irs"),
networkSendManuallyPumped = networkSendManuallyPumped,
threadPerNode = runAsync,
cordappPackages = listOf("net.corda.finance.contract", "net.corda.irs"))
threadPerNode = runAsync)
// TODO: Regulatory nodes don't actually exist properly, this is a last minute demo request.
// So we just fire a message at a node that doesn't know how to handle it, and it'll ignore it.
// But that's fine for visualisation purposes.

View File

@ -192,7 +192,8 @@ class InMemoryMessagingNetwork private constructor(
}
/**
* Mock service loadbalancing
* How traffic is allocated in the case where multiple nodes share a single identity, which happens for notaries
* in a cluster. You don't normally ever need to change this: it is mostly useful for testing notary implementations.
*/
@DoNotImplement
sealed class ServicePeerAllocationStrategy {

View File

@ -12,9 +12,6 @@ import net.corda.node.internal.StartedNode
import net.corda.node.services.api.StartedNodeServices
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.messaging.MessagingService
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseTransaction
import net.corda.nodeapi.internal.persistence.TransactionIsolationLevel
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.setMessagingServiceSpy
@ -39,35 +36,46 @@ data class MockNodeParameters(
val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
val configOverrides: (NodeConfiguration) -> Any? = {},
val version: VersionInfo = MockServices.MOCK_VERSION_INFO) {
fun setForcedID(forcedID: Int?): MockNodeParameters = copy(forcedID = forcedID)
fun setLegalName(legalName: CordaX500Name?): MockNodeParameters = copy(legalName = legalName)
fun setEntropyRoot(entropyRoot: BigInteger): MockNodeParameters = copy(entropyRoot = entropyRoot)
fun setConfigOverrides(configOverrides: (NodeConfiguration) -> Any?): MockNodeParameters = copy(configOverrides = configOverrides)
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)
}
/** Helper builder for configuring a [InternalMockNetwork] from Java. */
/**
* Immutable builder for configuring a [MockNetwork]. Kotlin users can also use named parameters to the constructor
* of [MockNetwork], which is more convenient.
*
* @property networkSendManuallyPumped If true then messages will not be routed from sender to receiver until you use
* the [MockNetwork.runNetwork] method. This is useful for writing single-threaded unit test code that can examine the
* state of the mock network before and after a message is sent, without races and without the receiving node immediately
* sending a response. The default is false, so you must call runNetwork.
* @property threadPerNode If true then each node will be run in its own thread. This can result in race conditions in
* your code if not carefully written, but is more realistic and may help if you have flows in your app that do long
* blocking operations. The default is false.
* @property servicePeerAllocationStrategy How messages are load balanced in the case where a single compound identity
* is used by multiple nodes. You rarely if ever need to change that, it's primarily of interest to people testing
* notary code.
* @property notarySpecs The notaries to use in the mock network. By default you get one mock notary and that is usually sufficient.
*/
@Suppress("unused")
data class MockNetworkParameters(
val networkSendManuallyPumped: Boolean = false,
val threadPerNode: Boolean = false,
val servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random(),
val initialiseSerialization: Boolean = true,
val notarySpecs: List<MockNetworkNotarySpec> = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME)),
val maxTransactionSize: Int = Int.MAX_VALUE) {
fun setNetworkSendManuallyPumped(networkSendManuallyPumped: Boolean): MockNetworkParameters = copy(networkSendManuallyPumped = networkSendManuallyPumped)
fun setThreadPerNode(threadPerNode: Boolean): MockNetworkParameters = copy(threadPerNode = threadPerNode)
fun setServicePeerAllocationStrategy(servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy): MockNetworkParameters = copy(servicePeerAllocationStrategy = servicePeerAllocationStrategy)
fun setInitialiseSerialization(initialiseSerialization: Boolean): MockNetworkParameters = copy(initialiseSerialization = initialiseSerialization)
fun setNotarySpecs(notarySpecs: List<MockNetworkNotarySpec>): MockNetworkParameters = copy(notarySpecs = notarySpecs)
fun setMaxTransactionSize(maxTransactionSize: Int): MockNetworkParameters = copy(maxTransactionSize = maxTransactionSize)
val notarySpecs: List<MockNetworkNotarySpec> = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME))) {
fun withNetworkSendManuallyPumped(networkSendManuallyPumped: Boolean): MockNetworkParameters = copy(networkSendManuallyPumped = networkSendManuallyPumped)
fun withThreadPerNode(threadPerNode: Boolean): MockNetworkParameters = copy(threadPerNode = threadPerNode)
fun withServicePeerAllocationStrategy(servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy): MockNetworkParameters = copy(servicePeerAllocationStrategy = servicePeerAllocationStrategy)
fun withNotarySpecs(notarySpecs: List<MockNetworkNotarySpec>): MockNetworkParameters = copy(notarySpecs = notarySpecs)
}
/** Represents a node configuration for injection via [MockNetworkParameters] **/
/** Represents a node configuration for injection via [MockNetworkParameters]. */
data class MockNetworkNotarySpec(val name: CordaX500Name, val validating: Boolean = true) {
constructor(name: CordaX500Name) : this(name, validating = true)
}
/** A class that represents an unstarted mock node for testing. **/
/** A class that represents an unstarted mock node for testing. */
class UnstartedMockNode private constructor(private val node: InternalMockNetwork.MockNode) {
companion object {
internal fun create(node: InternalMockNetwork.MockNode): UnstartedMockNode {
@ -80,7 +88,7 @@ class UnstartedMockNode private constructor(private val node: InternalMockNetwor
fun start() = StartedMockNode.create(node.start())
}
/** A class that represents a started mock node for testing. **/
/** A class that represents a started mock node for testing. */
class StartedMockNode private constructor(private val node: StartedNode<InternalMockNetwork.MockNode>) {
companion object {
internal fun create(node: StartedNode<InternalMockNetwork.MockNode>): StartedMockNode {
@ -128,9 +136,14 @@ class StartedMockNode private constructor(private val node: StartedNode<Internal
* Components that do IO are either swapped out for mocks, or pointed to a [Jimfs] in memory filesystem or an in
* memory H2 database instance.
*
* Java users can use the constructor that takes an (optional) [MockNetworkParameters] builder, which may be more
* convenient than specifying all the defaults by hand. Please see [MockNetworkParameters] for the documentation
* of each parameter.
*
* Mock network nodes require manual pumping by default: they will not run asynchronous. This means that
* for message exchanges to take place (and associated handlers to run), you must call the [runNetwork]
* method.
* method. If you want messages to flow automatically, use automatic pumping with a thread per node but watch out
* for code running parallel to your unit tests: you will need to use futures correctly to ensure race-free results.
*
* You can get a printout of every message sent by using code like:
*
@ -139,23 +152,28 @@ class StartedMockNode private constructor(private val node: StartedNode<Internal
* By default a single notary node is automatically started, which forms part of the network parameters for all the nodes.
* This node is available by calling [defaultNotaryNode].
*/
@Suppress("MemberVisibilityCanBePrivate", "CanBeParameter")
open class MockNetwork(
val cordappPackages: List<String>,
val defaultParameters: MockNetworkParameters = MockNetworkParameters(),
val networkSendManuallyPumped: Boolean = defaultParameters.networkSendManuallyPumped,
val threadPerNode: Boolean = defaultParameters.threadPerNode,
val servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy,
val initialiseSerialization: Boolean = defaultParameters.initialiseSerialization,
val notarySpecs: List<MockNetworkNotarySpec> = defaultParameters.notarySpecs,
val maxTransactionSize: Int = defaultParameters.maxTransactionSize) {
val notarySpecs: List<MockNetworkNotarySpec> = defaultParameters.notarySpecs
) {
@JvmOverloads
constructor(cordappPackages: List<String>, parameters: MockNetworkParameters = MockNetworkParameters()) : this(cordappPackages, defaultParameters = parameters)
private val internalMockNetwork: InternalMockNetwork = InternalMockNetwork(cordappPackages, defaultParameters, networkSendManuallyPumped, threadPerNode, servicePeerAllocationStrategy, initialiseSerialization, notarySpecs, maxTransactionSize)
val defaultNotaryNode get() : StartedMockNode = StartedMockNode.create(internalMockNetwork.defaultNotaryNode)
val defaultNotaryIdentity get() : Party = internalMockNetwork.defaultNotaryIdentity
val notaryNodes get() : List<StartedMockNode> = internalMockNetwork.notaryNodes.map { StartedMockNode.create(it) }
val nextNodeId get() : Int = internalMockNetwork.nextNodeId
private val internalMockNetwork: InternalMockNetwork = InternalMockNetwork(cordappPackages, defaultParameters, networkSendManuallyPumped, threadPerNode, servicePeerAllocationStrategy, notarySpecs)
/** Which node will be used as the primary notary during transaction builds. */
val defaultNotaryNode get(): StartedMockNode = StartedMockNode.create(internalMockNetwork.defaultNotaryNode)
/** The [Party] of the [defaultNotaryNode] */
val defaultNotaryIdentity get(): Party = internalMockNetwork.defaultNotaryIdentity
/** A list of all notary nodes in the network that have been started. */
val notaryNodes get(): List<StartedMockNode> = internalMockNetwork.notaryNodes.map { StartedMockNode.create(it) }
/** In a mock network, nodes have an incrementing integer ID. Real networks do not have this. Returns the next ID that will be used. */
val nextNodeId get(): Int = internalMockNetwork.nextNodeId
/** Create a started node with the given identity. **/
fun createPartyNode(legalName: CordaX500Name? = null): StartedMockNode = StartedMockNode.create(internalMockNetwork.createPartyNode(legalName))
@ -163,7 +181,9 @@ open class MockNetwork(
/** Create a started node with the given parameters. **/
fun createNode(parameters: MockNodeParameters = MockNodeParameters()): StartedMockNode = StartedMockNode.create(internalMockNetwork.createNode(parameters))
/** Create a started node with the given parameters.
/**
* Create a started node with the given parameters.
*
* @param legalName the node's legal name.
* @param forcedID a unique identifier for the node.
* @param entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value,
@ -184,7 +204,9 @@ open class MockNetwork(
/** Create an unstarted node with the given parameters. **/
fun createUnstartedNode(parameters: MockNodeParameters = MockNodeParameters()): UnstartedMockNode = UnstartedMockNode.create(internalMockNetwork.createUnstartedNode(parameters))
/** Create an unstarted node with the given parameters.
/**
* Create an unstarted node with the given parameters.
*
* @param legalName the node's legal name.
* @param forcedID a unique identifier for the node.
* @param entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value,

View File

@ -76,28 +76,11 @@ data class MockNodeArgs(
val version: VersionInfo = MOCK_VERSION_INFO
)
/**
* A mock node brings up a suite of in-memory services in a fast manner suitable for unit testing.
* Components that do IO are either swapped out for mocks, or pointed to a [Jimfs] in memory filesystem or an in
* memory H2 database instance.
*
* Mock network nodes require manual pumping by default: they will not run asynchronous. This means that
* for message exchanges to take place (and associated handlers to run), you must call the [runNetwork]
* method.
*
* You can get a printout of every message sent by using code like:
*
* LogHelper.setLevel("+messages")
*
* By default a single notary node is automatically started, which forms part of the network parameters for all the nodes.
* This node is available by calling [defaultNotaryNode].
*/
open class InternalMockNetwork(private val cordappPackages: List<String>,
defaultParameters: MockNetworkParameters = MockNetworkParameters(),
val networkSendManuallyPumped: Boolean = defaultParameters.networkSendManuallyPumped,
val threadPerNode: Boolean = defaultParameters.threadPerNode,
servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy,
initialiseSerialization: Boolean = defaultParameters.initialiseSerialization,
val notarySpecs: List<MockNetworkNotarySpec> = defaultParameters.notarySpecs,
maxTransactionSize: Int = Int.MAX_VALUE,
val defaultFactory: (MockNodeArgs) -> MockNode = InternalMockNetwork::MockNode) {
@ -118,7 +101,7 @@ open class InternalMockNetwork(private val cordappPackages: List<String>,
private val networkParameters: NetworkParametersCopier
private val _nodes = mutableListOf<MockNode>()
private val serializationEnv = try {
setGlobalSerialization(initialiseSerialization)
setGlobalSerialization(true)
} catch (e: IllegalStateException) {
throw IllegalStateException("Using more than one InternalMockNetwork simultaneously is not supported.", e)
}

View File

@ -13,7 +13,7 @@ public class MockNodeFactoryInJavaTest {
private static void factoryIsEasyToPassInUsingJava() {
//noinspection Convert2MethodRef
new MockNetwork(emptyList());
new MockNetwork(emptyList(), new MockNetworkParameters().setInitialiseSerialization(false));
new MockNetwork(emptyList(), new MockNetworkParameters().withThreadPerNode(true));
//noinspection Convert2MethodRef
new MockNetwork(emptyList()).createNode(new MockNodeParameters());
}

View File

@ -9,7 +9,7 @@ class InternalMockNetworkTests {
fun `does not leak serialization env if init fails`() {
val e = Exception("didn't work")
assertThatThrownBy {
object : InternalMockNetwork(emptyList(), initialiseSerialization = true) {
object : InternalMockNetwork(emptyList()) {
override fun createNotaries() = throw e
}
}.isSameAs(e)