Merged in rnicoll-test-in-memory-network (pull request #62)

Remove TestWithInMemoryNetwork
This commit is contained in:
Ross Nicoll 2016-04-20 13:37:35 +01:00
commit 9a17a1c144
6 changed files with 102 additions and 143 deletions

View File

@ -48,18 +48,18 @@ class MockNetwork(private val threadPerNode: Boolean = false,
/** Allows customisation of how nodes are created. */
interface Factory {
fun create(dir: Path, config: NodeConfiguration, network: MockNetwork,
timestamperAddr: NodeInfo?): MockNode
timestamperAddr: NodeInfo?, id: Int): MockNode
}
object DefaultFactory : Factory {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork,
timestamperAddr: NodeInfo?): MockNode {
return MockNode(dir, config, network, timestamperAddr)
timestamperAddr: NodeInfo?, id: Int): MockNode {
return MockNode(dir, config, network, timestamperAddr, id)
}
}
open class MockNode(dir: Path, config: NodeConfiguration, val mockNet: MockNetwork,
withTimestamper: NodeInfo?, val forcedID: Int = -1) : AbstractNode(dir, config, withTimestamper, Clock.systemUTC()) {
withTimestamper: NodeInfo?, val id: Int) : AbstractNode(dir, config, withTimestamper, Clock.systemUTC()) {
override val log: Logger = loggerFor<MockNode>()
override val serverThread: ExecutorService =
if (mockNet.threadPerNode)
@ -71,10 +71,8 @@ class MockNetwork(private val threadPerNode: Boolean = false,
// through the java.nio API which we are already mocking via Jimfs.
override fun makeMessagingService(): MessagingService {
if (forcedID == -1)
return mockNet.messagingNetwork.createNode(!mockNet.threadPerNode).second.start().get()
else
return mockNet.messagingNetwork.createNodeWithID(!mockNet.threadPerNode, forcedID).start().get()
require(id >= 0) { "Node ID must be zero or positive, was passed: " + id }
return mockNet.messagingNetwork.createNodeWithID(!mockNet.threadPerNode, id).start().get()
}
override fun makeIdentityService() = FixedIdentityService(mockNet.identities)
@ -92,7 +90,7 @@ class MockNetwork(private val threadPerNode: Boolean = false,
}
/** Returns a started node, optionally created by the passed factory method */
fun createNode(withTimestamper: NodeInfo?, forcedID: Int = -1, nodeFactory: Factory = defaultFactory,
fun createNode(withTimestamper: NodeInfo? = null, forcedID: Int = -1, nodeFactory: Factory = defaultFactory,
advertisedServices: Set<ServiceType> = emptySet()): MockNode {
val newNode = forcedID == -1
val id = if (newNode) counter++ else forcedID
@ -105,7 +103,7 @@ class MockNetwork(private val threadPerNode: Boolean = false,
override val exportJMXto: String = ""
override val nearestCity: String = "Atlantis"
}
val node = nodeFactory.create(path, config, this, withTimestamper).start()
val node = nodeFactory.create(path, config, this, withTimestamper, id).start()
node.info.advertisedServices = advertisedServices
_nodes.add(node)
return node

View File

@ -32,14 +32,14 @@ abstract class Simulation(val runAsync: Boolean,
// This puts together a mock network of SimulatedNodes.
open class SimulatedNode(dir: Path, config: NodeConfiguration, mockNet: MockNetwork,
withTimestamper: NodeInfo?) : MockNetwork.MockNode(dir, config, mockNet, withTimestamper) {
withTimestamper: NodeInfo?, id: Int) : MockNetwork.MockNode(dir, config, mockNet, withTimestamper, id) {
override fun findMyLocation(): PhysicalLocation? = CityDatabase[configuration.nearestCity]
}
inner class BankFactory : MockNetwork.Factory {
var counter = 0
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?): MockNetwork.MockNode {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?, id: Int): MockNetwork.MockNode {
val letter = 'A' + counter
val city = bankLocations[counter++ % bankLocations.size]
val cfg = object : NodeConfiguration {
@ -48,7 +48,7 @@ abstract class Simulation(val runAsync: Boolean,
override val exportJMXto: String = ""
override val nearestCity: String = city
}
return SimulatedNode(dir, cfg, network, timestamperAddr)
return SimulatedNode(dir, cfg, network, timestamperAddr, id)
}
fun createAll(): List<SimulatedNode> = bankLocations.map { network.createNode(timestamper.info, nodeFactory = this) as SimulatedNode }
@ -57,25 +57,25 @@ abstract class Simulation(val runAsync: Boolean,
val bankFactory = BankFactory()
object TimestampingNodeFactory : MockNetwork.Factory {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?): MockNetwork.MockNode {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?, id: Int): MockNetwork.MockNode {
val cfg = object : NodeConfiguration {
override val myLegalName: String = "Timestamping Service" // A magic string recognised by the CP contract
override val exportJMXto: String = ""
override val nearestCity: String = "Zurich"
}
return SimulatedNode(dir, cfg, network, timestamperAddr)
return SimulatedNode(dir, cfg, network, timestamperAddr, id)
}
}
object RatesOracleFactory : MockNetwork.Factory {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?): MockNetwork.MockNode {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?, id: Int): MockNetwork.MockNode {
val cfg = object : NodeConfiguration {
override val myLegalName: String = "Rates Service Provider"
override val exportJMXto: String = ""
override val nearestCity: String = "Madrid"
}
val n = object : SimulatedNode(dir, cfg, network, timestamperAddr) {
val n = object : SimulatedNode(dir, cfg, network, timestamperAddr, id) {
override fun makeInterestRatesOracleService() {
super.makeInterestRatesOracleService()
interestRatesService.upload(javaClass.getResourceAsStream("example.rates.txt"))
@ -86,14 +86,14 @@ abstract class Simulation(val runAsync: Boolean,
}
object RegulatorFactory : MockNetwork.Factory {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?): MockNetwork.MockNode {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?, id: Int): MockNetwork.MockNode {
val cfg = object : NodeConfiguration {
override val myLegalName: String = "Regulator A"
override val exportJMXto: String = ""
override val nearestCity: String = "Paris"
}
val n = object : SimulatedNode(dir, cfg, network, timestamperAddr) {
val n = object : SimulatedNode(dir, cfg, network, timestamperAddr, id) {
// 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

@ -87,8 +87,8 @@ class AttachmentTests {
fun maliciousResponse() {
// Make a node that doesn't do sanity checking at load time.
val n0 = network.createNode(null, nodeFactory = object : MockNetwork.Factory {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?): MockNetwork.MockNode {
return object : MockNetwork.MockNode(dir, config, network, timestamperAddr) {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?, id: Int): MockNetwork.MockNode {
return object : MockNetwork.MockNode(dir, config, network, timestamperAddr, id) {
override fun start(): MockNetwork.MockNode {
super.start()
(storage.attachments as NodeAttachmentService).checkAttachmentsOnLoad = false

View File

@ -3,57 +3,24 @@
package core.messaging
import core.serialization.deserialize
import core.testing.InMemoryMessagingNetwork
import org.junit.After
import core.testing.MockNetwork
import org.junit.Before
import org.junit.Test
import java.util.*
import kotlin.test.assertEquals
import kotlin.test.assertFails
import kotlin.test.assertFalse
import kotlin.test.assertTrue
open class TestWithInMemoryNetwork {
val nodes: MutableMap<InMemoryMessagingNetwork.Handle, InMemoryMessagingNetwork.InMemoryMessaging> = HashMap()
lateinit var network: InMemoryMessagingNetwork
class InMemoryMessagingTests {
lateinit var network: MockNetwork
fun makeNode(inBackground: Boolean = false): Pair<InMemoryMessagingNetwork.Handle, InMemoryMessagingNetwork.InMemoryMessaging> {
// The manuallyPumped = true bit means that we must call the pump method on the system in order to
val (address, builder) = network.createNode(!inBackground)
val node = builder.start().get()
nodes[address] = node
return Pair(address, node)
init {
// BriefLogFormatter.initVerbose()
}
@Before
fun setupNetwork() {
network = InMemoryMessagingNetwork()
nodes.clear()
}
@After
fun stopNetwork() {
network.stop()
}
fun pumpAll(blocking: Boolean) = network.endpoints.map { it.pump(blocking) }
// Keep calling "pump" in rounds until every node in the network reports that it had nothing to do
fun <T> runNetwork(body: () -> T): T {
val result = body()
runNetwork()
return result
}
fun runNetwork() {
while (pumpAll(false).any { it }) {
}
}
}
class InMemoryMessagingTests : TestWithInMemoryNetwork() {
init {
// BriefLogFormatter.initVerbose()
fun setUp() {
network = MockNetwork()
}
@Test
@ -73,46 +40,45 @@ class InMemoryMessagingTests : TestWithInMemoryNetwork() {
@Test
fun basics() {
val (addr1, node1) = makeNode()
val (addr2, node2) = makeNode()
val (addr3, node3) = makeNode()
val node1 = network.createNode()
val node2 = network.createNode()
val node3 = network.createNode()
val bits = "test-content".toByteArray()
var finalDelivery: Message? = null
with(node2) {
addMessageHandler { msg, registration ->
send(msg, addr3)
node2.net.addMessageHandler { msg, registration ->
node2.net.send(msg, node3.info.address)
}
}
with(node3) {
addMessageHandler { msg, registration ->
node2.net.addMessageHandler { msg, registration ->
finalDelivery = msg
}
}
// Node 1 sends a message and it should end up in finalDelivery, after we pump each node.
runNetwork {
node1.send(node1.createMessage("test.topic", bits), addr2)
}
// Node 1 sends a message and it should end up in finalDelivery, after we run the network
node1.net.send(node1.net.createMessage("test.topic", bits), node2.info.address)
network.runNetwork(rounds = 1)
assertTrue(Arrays.equals(finalDelivery!!.data, bits))
}
@Test
fun broadcast() {
val (addr1, node1) = makeNode()
val (addr2, node2) = makeNode()
val (addr3, node3) = makeNode()
val node1 = network.createNode()
val node2 = network.createNode()
val node3 = network.createNode()
val bits = "test-content".toByteArray()
var counter = 0
listOf(node1, node2, node3).forEach { it.addMessageHandler { msg, registration -> counter++ } }
runNetwork {
node1.send(node2.createMessage("test.topic", bits), network.everyoneOnline)
}
listOf(node1, node2, node3).forEach { it.net.addMessageHandler { msg, registration -> counter++ } }
node1.net.send(node2.net.createMessage("test.topic", bits), network.messagingNetwork.everyoneOnline)
network.runNetwork(rounds = 1)
assertEquals(3, counter)
}
@ -121,34 +87,34 @@ class InMemoryMessagingTests : TestWithInMemoryNetwork() {
// Test (re)delivery of messages to nodes that aren't created yet, or were stopped and then restarted.
// The purpose of this functionality is to simulate a reliable messaging system that keeps trying until
// messages are delivered.
val (addr1, node1) = makeNode()
var (addr2, node2) = makeNode()
val node1 = network.createNode()
var node2 = network.createNode()
node1.send("test.topic", addr2, "hello!")
node2.pump(false) // No handler registered, so the message goes into a holding area.
node1.net.send("test.topic", node2.info.address, "hello!")
network.runNetwork(rounds = 1) // No handler registered, so the message goes into a holding area.
var runCount = 0
node2.addMessageHandler("test.topic") { msg, registration ->
node2.net.addMessageHandler("test.topic") { msg, registration ->
if (msg.data.deserialize<String>() == "hello!")
runCount++
}
node2.pump(false) // Try again now the handler is registered
network.runNetwork(rounds = 1) // Try again now the handler is registered
assertEquals(1, runCount)
// Shut node2 down for a while. Node 1 keeps sending it messages though.
node2.stop()
node1.send("test.topic", addr2, "are you there?")
node1.send("test.topic", addr2, "wake up!")
node1.net.send("test.topic", node2.info.address, "are you there?")
node1.net.send("test.topic", node2.info.address, "wake up!")
// Now re-create node2 with the same address as last time, and re-register a message handler.
// Check that the messages that were sent whilst it was gone are still there, waiting for it.
node2 = network.createNodeWithID(true, addr2.id).start().get()
node2.addMessageHandler("test.topic") { a, b -> runCount++ }
assertTrue(node2.pump(false))
node2 = network.createNode(null, node2.id)
node2.net.addMessageHandler("test.topic") { a, b -> runCount++ }
network.runNetwork(rounds = 1)
assertEquals(2, runCount)
assertTrue(node2.pump(false))
network.runNetwork(rounds = 1)
assertEquals(3, runCount)
assertFalse(node2.pump(false))
network.runNetwork(rounds = 1)
assertEquals(3, runCount)
}
}
}

View File

@ -35,7 +35,7 @@ import kotlin.test.assertTrue
*
* We assume that Alice and Bob already found each other via some market, and have agreed the details already.
*/
class TwoPartyTradeProtocolTests : TestWithInMemoryNetwork() {
class TwoPartyTradeProtocolTests {
lateinit var net: MockNetwork
@Before
@ -154,8 +154,8 @@ class TwoPartyTradeProtocolTests : TestWithInMemoryNetwork() {
// ... bring the node back up ... the act of constructing the SMM will re-register the message handlers
// that Bob was waiting on before the reboot occurred.
bobNode = net.createNode(timestamperAddr, bobAddr.id, object : MockNetwork.Factory {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?): MockNetwork.MockNode {
return object : MockNetwork.MockNode(dir, config, net, timestamperAddr, bobAddr.id) {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?, id: Int): MockNetwork.MockNode {
return object : MockNetwork.MockNode(dir, config, network, timestamperAddr, bobAddr.id) {
override fun initialiseStorageService(dir: Path): StorageService {
val ss = super.initialiseStorageService(dir)
val smMap = ss.stateMachines
@ -184,9 +184,9 @@ class TwoPartyTradeProtocolTests : TestWithInMemoryNetwork() {
// of gets and puts.
private fun makeNodeWithTracking(name: String): MockNetwork.MockNode {
// Create a node in the mock network ...
return net.createNode(null, nodeFactory = object : MockNetwork.Factory {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?): MockNetwork.MockNode {
return object : MockNetwork.MockNode(dir, config, network, timestamperAddr) {
return net.createNode(nodeFactory = object : MockNetwork.Factory {
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?, id: Int): MockNetwork.MockNode {
return object : MockNetwork.MockNode(dir, config, network, timestamperAddr, id) {
// That constructs the storage service object in a customised way ...
override fun constructStorageService(attachments: NodeAttachmentService, keypair: KeyPair, identity: Party): StorageServiceImpl {
// To use RecordingMaps instead of ordinary HashMaps.

View File

@ -3,33 +3,27 @@ package core.node
import co.paralleluniverse.fibers.Suspendable
import core.*
import core.crypto.SecureHash
import core.messaging.StateMachineManager
import core.messaging.TestWithInMemoryNetwork
import core.node.services.*
import core.protocols.ProtocolLogic
import core.serialization.serialize
import core.testing.InMemoryMessagingNetwork
import core.testing.MockNetworkMapCache
import core.testutils.ALICE
import core.testutils.ALICE_KEY
import core.testing.MockNetwork
import core.testutils.CASH
import core.utilities.BriefLogFormatter
import org.junit.Before
import org.junit.Test
import protocols.TimestampingProtocol
import java.security.PublicKey
import java.time.Clock
import java.time.Instant
import java.time.ZoneId
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
// TODO: Refactor this to use MockNode.
class TimestamperNodeServiceTest {
lateinit var network: MockNetwork
class TimestamperNodeServiceTest : TestWithInMemoryNetwork() {
lateinit var myMessaging: Pair<InMemoryMessagingNetwork.Handle, InMemoryMessagingNetwork.InMemoryMessaging>
lateinit var serviceMessaging: Pair<InMemoryMessagingNetwork.Handle, InMemoryMessagingNetwork.InMemoryMessaging>
lateinit var service: NodeTimestamperService
init {
BriefLogFormatter.initVerbose("dlg.timestamping.request")
}
val ptx = TransactionBuilder().apply {
addInputState(StateRef(SecureHash.randomSHA256(), 0))
@ -37,25 +31,10 @@ class TimestamperNodeServiceTest : TestWithInMemoryNetwork() {
}
val clock = Clock.fixed(Instant.now(), ZoneId.systemDefault())
lateinit var mockServices: ServiceHub
lateinit var serverKey: PublicKey
init {
BriefLogFormatter.initVerbose("dlg.timestamping.request")
}
@Before
fun setup() {
myMessaging = makeNode()
serviceMessaging = makeNode()
mockServices = MockServices(net = serviceMessaging.second, storage = MockStorageService())
val timestampingNodeID = network.setupTimestampingNode(true).first
(mockServices.networkMapCache as MockNetworkMapCache).timestampingNodes.add(timestampingNodeID)
serverKey = timestampingNodeID.identity.owningKey
// And a separate one to be tested directly, to make the unit tests a bit faster.
service = NodeTimestamperService(serviceMessaging.second, Party("Unit test suite", ALICE), ALICE_KEY)
network = MockNetwork()
}
class TestPSM(val server: NodeInfo, val now: Instant) : ProtocolLogic<Boolean>() {
@ -76,57 +55,73 @@ class TimestamperNodeServiceTest : TestWithInMemoryNetwork() {
@Test
fun successWithNetwork() {
val psm = runNetwork {
val smm = StateMachineManager(MockServices(net = myMessaging.second), RunOnCallerThread)
val logName = NodeTimestamperService.TIMESTAMPING_PROTOCOL_TOPIC
val psm = TestPSM(mockServices.networkMapCache.timestampingNodes[0], clock.instant())
smm.add(logName, psm)
}
assertTrue(psm.isDone)
val timestamperNode = network.createNode(null, advertisedServices = setOf(TimestamperService.Type))
val logName = NodeTimestamperService.TIMESTAMPING_PROTOCOL_TOPIC
val psm = TestPSM(timestamperNode.info, clock.instant())
val future = timestamperNode.smm.add(logName, psm)
network.runNetwork()
assertTrue(future.isDone)
}
@Test
fun wrongCommands() {
val timestamperNode = network.createNode(null, advertisedServices = setOf(TimestamperService.Type))
val timestamperKey = timestamperNode.services.storageService.myLegalIdentity.owningKey
val service = timestamperNode.inNodeTimestampingService!!
// Zero commands is not OK.
assertFailsWith(TimestampingError.RequiresExactlyOneCommand::class) {
val wtx = ptx.toWireTransaction()
service.processRequest(TimestampingProtocol.Request(wtx.serialize(), myMessaging.first, Long.MIN_VALUE))
service.processRequest(TimestampingProtocol.Request(wtx.serialize(), timestamperNode.info.address, Long.MIN_VALUE))
}
// More than one command is not OK.
assertFailsWith(TimestampingError.RequiresExactlyOneCommand::class) {
ptx.addCommand(TimestampCommand(clock.instant(), 30.seconds), ALICE)
ptx.addCommand(TimestampCommand(clock.instant(), 40.seconds), ALICE)
ptx.addCommand(TimestampCommand(clock.instant(), 30.seconds), timestamperKey)
ptx.addCommand(TimestampCommand(clock.instant(), 40.seconds), timestamperKey)
val wtx = ptx.toWireTransaction()
service.processRequest(TimestampingProtocol.Request(wtx.serialize(), myMessaging.first, Long.MIN_VALUE))
service.processRequest(TimestampingProtocol.Request(wtx.serialize(), timestamperNode.info.address, Long.MIN_VALUE))
}
}
@Test
fun tooEarly() {
val timestamperNode = network.createNode(null, advertisedServices = setOf(TimestamperService.Type))
val timestamperKey = timestamperNode.services.storageService.myLegalIdentity.owningKey
val service = timestamperNode.inNodeTimestampingService!!
assertFailsWith(TimestampingError.NotOnTimeException::class) {
val now = clock.instant()
ptx.addCommand(TimestampCommand(now - 60.seconds, now - 40.seconds), ALICE)
ptx.addCommand(TimestampCommand(now - 60.seconds, now - 40.seconds), timestamperKey)
val wtx = ptx.toWireTransaction()
service.processRequest(TimestampingProtocol.Request(wtx.serialize(), myMessaging.first, Long.MIN_VALUE))
service.processRequest(TimestampingProtocol.Request(wtx.serialize(), timestamperNode.info.address, Long.MIN_VALUE))
}
}
@Test
fun tooLate() {
val timestamperNode = network.createNode(null, advertisedServices = setOf(TimestamperService.Type))
val timestamperKey = timestamperNode.services.storageService.myLegalIdentity.owningKey
val service = timestamperNode.inNodeTimestampingService!!
assertFailsWith(TimestampingError.NotOnTimeException::class) {
val now = clock.instant()
ptx.addCommand(TimestampCommand(now - 60.seconds, now - 40.seconds), ALICE)
ptx.addCommand(TimestampCommand(now - 60.seconds, now - 40.seconds), timestamperKey)
val wtx = ptx.toWireTransaction()
service.processRequest(TimestampingProtocol.Request(wtx.serialize(), myMessaging.first, Long.MIN_VALUE))
service.processRequest(TimestampingProtocol.Request(wtx.serialize(), timestamperNode.info.address, Long.MIN_VALUE))
}
}
@Test
fun success() {
val timestamperNode = network.createNode(null, advertisedServices = setOf(TimestamperService.Type))
val timestamperKey = timestamperNode.services.storageService.myLegalIdentity.owningKey
val service = timestamperNode.inNodeTimestampingService!!
val now = clock.instant()
ptx.addCommand(TimestampCommand(now - 20.seconds, now + 20.seconds), ALICE)
ptx.addCommand(TimestampCommand(now - 20.seconds, now + 20.seconds), timestamperKey)
val wtx = ptx.toWireTransaction()
val sig = service.processRequest(TimestampingProtocol.Request(wtx.serialize(), myMessaging.first, Long.MIN_VALUE))
val sig = service.processRequest(TimestampingProtocol.Request(wtx.serialize(), timestamperNode.info.address, Long.MIN_VALUE))
ptx.checkAndAddSignature(sig)
ptx.toSignedTransaction(false).verifySignatures()
}