Made FlowCheckpointVersionNodeStartupCheckTest leaner (#4640)

* Removed `restart node successfully with suspended flow` as it duplicates `TraderDemoTest#Test restart node during flow works properly`
* Removed the need for a notary
This commit is contained in:
Shams Asari
2019-01-28 14:08:54 +00:00
committed by GitHub
parent 49e23bca82
commit e20278fbfd
5 changed files with 112 additions and 106 deletions

22
.idea/compiler.xml generated
View File

@ -4,6 +4,11 @@
<bytecodeTargetLevel target="1.8"> <bytecodeTargetLevel target="1.8">
<module name="api-scanner_main" target="1.8" /> <module name="api-scanner_main" target="1.8" />
<module name="api-scanner_test" target="1.8" /> <module name="api-scanner_test" target="1.8" />
<module name="attachment-demo-contracts_main" target="1.8" />
<module name="attachment-demo-contracts_test" target="1.8" />
<module name="attachment-demo-workflows_integrationTest" target="1.8" />
<module name="attachment-demo-workflows_main" target="1.8" />
<module name="attachment-demo-workflows_test" target="1.8" />
<module name="attachment-demo_integrationTest" target="1.8" /> <module name="attachment-demo_integrationTest" target="1.8" />
<module name="attachment-demo_main" target="1.8" /> <module name="attachment-demo_main" target="1.8" />
<module name="attachment-demo_test" target="1.8" /> <module name="attachment-demo_test" target="1.8" />
@ -36,6 +41,8 @@
<module name="common-validation_test" target="1.8" /> <module name="common-validation_test" target="1.8" />
<module name="confidential-identities_main" target="1.8" /> <module name="confidential-identities_main" target="1.8" />
<module name="confidential-identities_test" target="1.8" /> <module name="confidential-identities_test" target="1.8" />
<module name="contracts-irs_main" target="1.8" />
<module name="contracts-irs_test" target="1.8" />
<module name="contracts-states_integrationTest" target="1.8" /> <module name="contracts-states_integrationTest" target="1.8" />
<module name="contracts-states_main" target="1.8" /> <module name="contracts-states_main" target="1.8" />
<module name="contracts-states_test" target="1.8" /> <module name="contracts-states_test" target="1.8" />
@ -64,6 +71,8 @@
<module name="corda-webserver_integrationTest" target="1.8" /> <module name="corda-webserver_integrationTest" target="1.8" />
<module name="corda-webserver_main" target="1.8" /> <module name="corda-webserver_main" target="1.8" />
<module name="corda-webserver_test" target="1.8" /> <module name="corda-webserver_test" target="1.8" />
<module name="cordapp-configuration-workflows_main" target="1.8" />
<module name="cordapp-configuration-workflows_test" target="1.8" />
<module name="cordapp-configuration_main" target="1.8" /> <module name="cordapp-configuration_main" target="1.8" />
<module name="cordapp-configuration_test" target="1.8" /> <module name="cordapp-configuration_test" target="1.8" />
<module name="cordapp_integrationTest" target="1.8" /> <module name="cordapp_integrationTest" target="1.8" />
@ -170,6 +179,10 @@
<module name="net.corda_canonicalizer_test" target="1.8" /> <module name="net.corda_canonicalizer_test" target="1.8" />
<module name="network-bootstrapper_main" target="1.8" /> <module name="network-bootstrapper_main" target="1.8" />
<module name="network-bootstrapper_test" target="1.8" /> <module name="network-bootstrapper_test" target="1.8" />
<module name="network-verifier-contracts_main" target="1.8" />
<module name="network-verifier-contracts_test" target="1.8" />
<module name="network-verifier-workflows_main" target="1.8" />
<module name="network-verifier-workflows_test" target="1.8" />
<module name="network-verifier_main" target="1.8" /> <module name="network-verifier_main" target="1.8" />
<module name="network-verifier_test" target="1.8" /> <module name="network-verifier_test" target="1.8" />
<module name="network-visualiser_main" target="1.8" /> <module name="network-visualiser_main" target="1.8" />
@ -189,6 +202,10 @@
<module name="node_test" target="1.8" /> <module name="node_test" target="1.8" />
<module name="notary-bft-smart_main" target="1.8" /> <module name="notary-bft-smart_main" target="1.8" />
<module name="notary-bft-smart_test" target="1.8" /> <module name="notary-bft-smart_test" target="1.8" />
<module name="notary-demo-contracts_main" target="1.8" />
<module name="notary-demo-contracts_test" target="1.8" />
<module name="notary-demo-workflows_main" target="1.8" />
<module name="notary-demo-workflows_test" target="1.8" />
<module name="notary-demo_main" target="1.8" /> <module name="notary-demo_main" target="1.8" />
<module name="notary-demo_test" target="1.8" /> <module name="notary-demo_test" target="1.8" />
<module name="notary-raft_main" target="1.8" /> <module name="notary-raft_main" target="1.8" />
@ -266,6 +283,11 @@
<module name="webserver_integrationTest" target="1.8" /> <module name="webserver_integrationTest" target="1.8" />
<module name="webserver_main" target="1.8" /> <module name="webserver_main" target="1.8" />
<module name="webserver_test" target="1.8" /> <module name="webserver_test" target="1.8" />
<module name="workflows-irs_main" target="1.8" />
<module name="workflows-irs_test" target="1.8" />
<module name="workflows-trader_integrationTest" target="1.8" />
<module name="workflows-trader_main" target="1.8" />
<module name="workflows-trader_test" target="1.8" />
<module name="workflows_integrationTest" target="1.8" /> <module name="workflows_integrationTest" target="1.8" />
<module name="workflows_main" target="1.8" /> <module name="workflows_main" target="1.8" />
<module name="workflows_test" target="1.8" /> <module name="workflows_test" target="1.8" />

View File

@ -63,6 +63,9 @@ fun Path.copyTo(target: Path, vararg options: CopyOption): Path = Files.copy(thi
/** @see Files.move */ /** @see Files.move */
fun Path.moveTo(target: Path, vararg options: CopyOption): Path = Files.move(this, target, *options) fun Path.moveTo(target: Path, vararg options: CopyOption): Path = Files.move(this, target, *options)
/** @see Files.move */
fun Path.renameTo(fileName: String, vararg options: CopyOption): Path = moveTo(parent / fileName, *options)
/** See overload of [Files.copy] which takes in an [InputStream]. */ /** See overload of [Files.copy] which takes in an [InputStream]. */
fun Path.copyTo(out: OutputStream): Long = Files.copy(this, out) fun Path.copyTo(out: OutputStream): Long = Files.copy(this, out)

View File

@ -1,12 +1,13 @@
package net.corda.node.flows package net.corda.node.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.internal.* import net.corda.core.internal.*
import net.corda.core.internal.concurrent.transpose import net.corda.core.messaging.startFlow
import net.corda.core.messaging.startTrackedFlow
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.unwrap
import net.corda.node.internal.CheckpointIncompatibleException import net.corda.node.internal.CheckpointIncompatibleException
import net.corda.node.internal.NodeStartup
import net.corda.testMessage.*
import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.singleIdentity import net.corda.testing.core.singleIdentity
@ -14,65 +15,30 @@ import net.corda.testing.driver.DriverDSL
import net.corda.testing.driver.DriverParameters import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.NodeParameters import net.corda.testing.driver.NodeParameters
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
import net.corda.testing.node.TestCordapp import net.corda.testing.node.internal.CustomCordapp
import net.corda.testing.node.internal.* import net.corda.testing.node.internal.ListenProcessDeathException
import net.test.cordapp.v1.Record import net.corda.testing.node.internal.assertCheckpoints
import net.test.cordapp.v1.SendMessageFlow import net.corda.testing.node.internal.enclosedCordapp
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test import org.junit.Test
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.StandardCopyOption.REPLACE_EXISTING import java.nio.file.StandardCopyOption.REPLACE_EXISTING
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
// TraderDemoTest already has a test which checks the node can resume a flow from a checkpoint
class FlowCheckpointVersionNodeStartupCheckTest { class FlowCheckpointVersionNodeStartupCheckTest {
companion object { companion object {
val message = Message("Hello world!") val defaultCordapp = enclosedCordapp()
val defaultCordapp = cordappWithPackages(
MessageState::class.packageName, SendMessageFlow::class.packageName
)
}
@Test
fun `restart node successfully with suspended flow`() {
return driver(parametersForRestartingNodes()) {
createSuspendedFlowInBob(setOf(defaultCordapp))
// 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(30, 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)
}
} }
@Test @Test
fun `restart node with incompatible version of suspended flow due to different jar name`() { fun `restart node with incompatible version of suspended flow due to different jar name`() {
driver(parametersForRestartingNodes()) { driver(parametersForRestartingNodes()) {
val uniqueName = "different-jar-name-test-${UUID.randomUUID()}" val defaultCordappJar = createSuspendedFlowInBob()
val cordapp = defaultCordapp.copy(name = uniqueName) defaultCordappJar.renameTo("renamed-${defaultCordappJar.fileName}")
val bobBaseDir = createSuspendedFlowInBob(setOf(cordapp))
val cordappsDir = bobBaseDir / "cordapps"
val cordappJar = cordappsDir.list().single { it.toString().endsWith(".jar") }
// Make sure we're dealing with right jar
assertThat(cordappJar.fileName.toString()).contains(uniqueName)
// Rename the jar file.
cordappJar.moveTo(cordappsDir / "renamed-${cordappJar.fileName}")
assertBobFailsToStartWithLogMessage( assertBobFailsToStartWithLogMessage(
emptyList(), CheckpointIncompatibleException.FlowNotInstalledException(ReceiverFlow::class.java).message
CheckpointIncompatibleException.FlowNotInstalledException(SendMessageFlow::class.java).message
) )
} }
} }
@ -80,54 +46,42 @@ class FlowCheckpointVersionNodeStartupCheckTest {
@Test @Test
fun `restart node with incompatible version of suspended flow due to different jar hash`() { fun `restart node with incompatible version of suspended flow due to different jar hash`() {
driver(parametersForRestartingNodes()) { driver(parametersForRestartingNodes()) {
val uniqueWorkflowJarName = "different-jar-hash-test-${UUID.randomUUID()}" val defaultCordappJar = createSuspendedFlowInBob()
val uniqueContractJarName = "contract-$uniqueWorkflowJarName"
val defaultWorkflowJar = cordappWithPackages(SendMessageFlow::class.packageName)
val defaultContractJar = cordappWithPackages(MessageState::class.packageName)
val contractJar = defaultContractJar.copy(name = uniqueContractJarName)
val workflowJar = defaultWorkflowJar.copy(name = uniqueWorkflowJarName)
val bobBaseDir = createSuspendedFlowInBob(setOf(workflowJar, contractJar))
val cordappsDir = bobBaseDir / "cordapps"
val cordappJar = cordappsDir.list().single {
! it.toString().contains(uniqueContractJarName) && it.toString().endsWith(".jar")
}
// Make sure we're dealing with right jar
assertThat(cordappJar.fileName.toString()).contains(uniqueWorkflowJarName)
// The name is part of the MANIFEST so changing it is sufficient to change the jar hash // The name is part of the MANIFEST so changing it is sufficient to change the jar hash
val modifiedCordapp = workflowJar.copy(name = "${workflowJar.name}-modified") val modifiedCordapp = defaultCordapp.copy(name = "${defaultCordapp.name}-modified")
val modifiedCordappJar = CustomCordapp.getJarFile(modifiedCordapp) val modifiedCordappJar = CustomCordapp.getJarFile(modifiedCordapp)
modifiedCordappJar.moveTo(cordappJar, REPLACE_EXISTING) modifiedCordappJar.moveTo(defaultCordappJar, REPLACE_EXISTING)
assertBobFailsToStartWithLogMessage( assertBobFailsToStartWithLogMessage(
emptyList(),
// The part of the log message generated by CheckpointIncompatibleException.FlowVersionIncompatibleException // The part of the log message generated by CheckpointIncompatibleException.FlowVersionIncompatibleException
"that is incompatible with the current installed version of" "that is incompatible with the current installed version of"
) )
} }
} }
private fun DriverDSL.createSuspendedFlowInBob(cordapps: Set<TestCordapp>): Path { private fun DriverDSL.createSuspendedFlowInBob(): Path {
val (alice, bob) = listOf(ALICE_NAME, BOB_NAME) val (alice, bob) = listOf(
.map { startNode(NodeParameters(providedName = it, additionalCordapps = cordapps)) } startNode(providedName = ALICE_NAME),
.transpose() startNode(NodeParameters(providedName = BOB_NAME, additionalCordapps = listOf(defaultCordapp)))
.getOrThrow() ).map { it.getOrThrow() }
alice.stop()
val flowTracker = bob.rpc.startTrackedFlow(::SendMessageFlow, message, defaultNotaryIdentity, alice.nodeInfo.singleIdentity()).progress alice.stop() // Stop Alice so that Bob never receives the message
// Wait until Bob progresses as far as possible because Alice node is offline
flowTracker.takeFirst { it == SendMessageFlow.Companion.FINALISING_TRANSACTION.label }.toBlocking().single() bob.rpc.startFlow(::ReceiverFlow, alice.nodeInfo.singleIdentity())
// Wait until Bob's flow has started
bob.rpc.stateMachinesFeed().let { it.updates.map { it.id }.startWith(it.snapshot.map { it.id }) }.toBlocking().first()
bob.stop() bob.stop()
return bob.baseDirectory assertCheckpoints(BOB_NAME, 1)
return (bob.baseDirectory / "cordapps").list().single { it.toString().endsWith(".jar") }
} }
private fun DriverDSL.assertBobFailsToStartWithLogMessage(cordapps: Collection<TestCordapp>, logMessage: String) { private fun DriverDSL.assertBobFailsToStartWithLogMessage(logMessage: String) {
assertFailsWith(ListenProcessDeathException::class) { assertFailsWith(ListenProcessDeathException::class) {
startNode(NodeParameters( startNode(NodeParameters(
providedName = BOB_NAME, providedName = BOB_NAME,
customOverrides = mapOf("devMode" to false), customOverrides = mapOf("devMode" to false)
additionalCordapps = cordapps
)).getOrThrow() )).getOrThrow()
} }
@ -141,7 +95,21 @@ class FlowCheckpointVersionNodeStartupCheckTest {
return DriverParameters( return DriverParameters(
startNodesInProcess = false, // Start nodes in separate processes to ensure CordappLoader is not shared between restarts 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 inMemoryDB = false, // Ensure database is persisted between node restarts so we can keep suspended flows
cordappsForAllNodes = emptyList() cordappsForAllNodes = emptyList(),
notarySpecs = emptyList()
) )
} }
@InitiatingFlow
@StartableByRPC
class ReceiverFlow(private val otherParty: Party) : FlowLogic<String>() {
@Suspendable
override fun call(): String = initiateFlow(otherParty).receive<String>().unwrap { it }
}
@InitiatedBy(ReceiverFlow::class)
class SenderFlow(private val otherSide: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() = otherSide.send("Hello!")
}
} }

View File

@ -1,27 +1,33 @@
package net.corda.traderdemo package net.corda.traderdemo
import net.corda.client.rpc.CordaRPCClient import net.corda.client.rpc.CordaRPCClient
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.div
import net.corda.core.messaging.startFlow import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.millis import net.corda.core.utilities.millis
import net.corda.finance.DOLLARS import net.corda.finance.DOLLARS
import net.corda.finance.USD
import net.corda.finance.contracts.getCashBalance
import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow import net.corda.finance.flows.CashPaymentFlow
import net.corda.node.services.Permissions.Companion.all import net.corda.node.services.Permissions.Companion.all
import net.corda.node.services.Permissions.Companion.startFlow import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.testing.core.* import net.corda.testing.core.BOC_NAME
import net.corda.testing.driver.DriverParameters import net.corda.testing.core.DUMMY_BANK_A_NAME
import net.corda.testing.driver.InProcess import net.corda.testing.core.DUMMY_BANK_B_NAME
import net.corda.testing.driver.OutOfProcess import net.corda.testing.core.singleIdentity
import net.corda.testing.driver.driver import net.corda.testing.driver.*
import net.corda.testing.node.TestCordapp import net.corda.testing.node.TestCordapp
import net.corda.testing.node.User import net.corda.testing.node.User
import net.corda.testing.node.internal.FINANCE_CORDAPPS import net.corda.testing.node.internal.FINANCE_CORDAPPS
import net.corda.testing.node.internal.assertCheckpoints
import net.corda.testing.node.internal.poll import net.corda.testing.node.internal.poll
import net.corda.traderdemo.flow.CommercialPaperIssueFlow import net.corda.traderdemo.flow.CommercialPaperIssueFlow
import net.corda.traderdemo.flow.SellerFlow import net.corda.traderdemo.flow.SellerFlow
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Test import org.junit.Test
import java.sql.DriverManager
import java.util.concurrent.Executors import java.util.concurrent.Executors
class TraderDemoTest { class TraderDemoTest {
@ -85,27 +91,20 @@ class TraderDemoTest {
inMemoryDB = false, inMemoryDB = false,
cordappsForAllNodes = FINANCE_CORDAPPS + TestCordapp.findCordapp("net.corda.traderdemo") cordappsForAllNodes = FINANCE_CORDAPPS + TestCordapp.findCordapp("net.corda.traderdemo")
)) { )) {
val demoUser = User("demo", "demo", setOf(startFlow<SellerFlow>(), all())) val (buyer, seller, bank) = listOf(
val bankUser = User("user1", "test", permissions = setOf(all())) startNode(providedName = DUMMY_BANK_A_NAME),
val (nodeA, nodeB, bankNode) = listOf( startNode(providedName = DUMMY_BANK_B_NAME),
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = listOf(demoUser)), startNode(providedName = BOC_NAME)
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser)), ).map { it.getOrThrow() }
startNode(providedName = BOC_NAME, rpcUsers = listOf(bankUser)) TraderDemoClientApi(bank.rpc).runIssuer(amount = 100.DOLLARS, buyerName = DUMMY_BANK_A_NAME, sellerName = DUMMY_BANK_B_NAME)
).map { (it.getOrThrow() as OutOfProcess) } val saleFuture = seller.rpc.startFlow(::SellerFlow, buyer.nodeInfo.singleIdentity(), 5.DOLLARS).returnValue
buyer.rpc.stateMachinesFeed().updates.toBlocking().first() // wait until initiated flow starts
val nodeBRpc = CordaRPCClient(nodeB.rpcAddress).start(demoUser.username, demoUser.password).proxy buyer.stop()
val nodeARpc = CordaRPCClient(nodeA.rpcAddress).start(demoUser.username, demoUser.password).proxy assertCheckpoints(DUMMY_BANK_A_NAME, 1)
val nodeBankRpc = let { val buyer2 = startNode(providedName = DUMMY_BANK_A_NAME, customOverrides = mapOf("p2pAddress" to buyer.p2pAddress.toString())).getOrThrow()
val client = CordaRPCClient(bankNode.rpcAddress) saleFuture.getOrThrow()
client.start(bankUser.username, bankUser.password).proxy assertThat(buyer2.rpc.getCashBalance(USD)).isEqualTo(95.DOLLARS)
} assertThat(seller.rpc.getCashBalance(USD)).isEqualTo(5.DOLLARS)
TraderDemoClientApi(nodeBankRpc).runIssuer(amount = 100.DOLLARS, buyerName = nodeA.nodeInfo.singleIdentity().name, sellerName = nodeB.nodeInfo.singleIdentity().name)
val stxFuture = nodeBRpc.startFlow(::SellerFlow, nodeA.nodeInfo.singleIdentity(), 5.DOLLARS).returnValue
nodeARpc.stateMachinesFeed().updates.toBlocking().first() // wait until initiated flow starts
nodeA.stop()
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to nodeA.p2pAddress.toString()))
stxFuture.getOrThrow()
} }
} }
} }

View File

@ -6,9 +6,11 @@ import net.corda.core.CordaException
import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.CordaFuture
import net.corda.core.context.InvocationContext import net.corda.core.context.InvocationContext
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.FlowStateMachine import net.corda.core.internal.FlowStateMachine
import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.div
import net.corda.core.internal.times import net.corda.core.internal.times
import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.CordaRPCOps
import net.corda.core.serialization.internal.SerializationEnvironment import net.corda.core.serialization.internal.SerializationEnvironment
@ -20,6 +22,7 @@ import net.corda.core.utilities.millis
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.node.services.api.StartedNodeServices import net.corda.node.services.api.StartedNodeServices
import net.corda.node.services.messaging.Message import net.corda.node.services.messaging.Message
import net.corda.testing.driver.DriverDSL
import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.NodeHandle
import net.corda.testing.internal.chooseIdentity import net.corda.testing.internal.chooseIdentity
import net.corda.testing.internal.createTestSerializationEnv import net.corda.testing.internal.createTestSerializationEnv
@ -29,12 +32,14 @@ import net.corda.testing.node.TestCordapp
import net.corda.testing.node.User import net.corda.testing.node.User
import net.corda.testing.node.testContext import net.corda.testing.node.testContext
import org.apache.commons.lang.ClassUtils import org.apache.commons.lang.ClassUtils
import org.assertj.core.api.Assertions.assertThat
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import rx.Observable import rx.Observable
import rx.subjects.AsyncSubject import rx.subjects.AsyncSubject
import java.io.InputStream import java.io.InputStream
import java.net.Socket import java.net.Socket
import java.net.SocketException import java.net.SocketException
import java.sql.DriverManager
import java.time.Duration import java.time.Duration
import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -239,6 +244,15 @@ fun CordaRPCOps.waitForShutdown(): Observable<Unit> {
return completable return completable
} }
fun DriverDSL.assertCheckpoints(name: CordaX500Name, expected: Long) {
DriverManager.getConnection("jdbc:h2:file:${baseDirectory(name) / "persistence"}", "sa", "").use { connection ->
connection.createStatement().executeQuery("select count(*) from NODE_CHECKPOINTS").use { rs ->
rs.next()
assertThat(rs.getLong(1)).isEqualTo(expected)
}
}
}
/** /**
* Should only be used by Driver and MockNode. * Should only be used by Driver and MockNode.
*/ */