Added tests to make sure the platform version is correctly available

This commit is contained in:
Shams Asari 2017-11-24 14:32:32 +00:00
parent 2ceb6283af
commit 4ca54b73fe
12 changed files with 125 additions and 85 deletions

View File

@ -6,8 +6,7 @@ buildscript {
// Our version: bump this on release. // Our version: bump this on release.
ext.corda_release_version = "3.0-SNAPSHOT" ext.corda_release_version = "3.0-SNAPSHOT"
// Increment this on any release that changes public APIs anywhere in the Corda platform // Increment this on any release that changes public APIs anywhere in the Corda platform
// TODO This is going to be difficult until we have a clear separation throughout the code of what is public and what is internal ext.corda_platform_version = constants.getProperty("platformVersion")
ext.corda_platform_version = 2
ext.gradle_plugins_version = constants.getProperty("gradlePluginsVersion") ext.gradle_plugins_version = constants.getProperty("gradlePluginsVersion")
// Dependency versions. Can run 'gradle dependencyUpdates' to find new versions of things. // Dependency versions. Can run 'gradle dependencyUpdates' to find new versions of things.

View File

@ -74,7 +74,7 @@ public class StandaloneCordaRPCJavaClientTest {
} }
private void copyFinanceCordapp() { private void copyFinanceCordapp() {
Path cordappsDir = (factory.baseDirectory(notaryConfig).resolve("cordapps")); Path cordappsDir = (factory.baseDirectory(notaryConfig).resolve(NodeProcess.CORDAPPS_DIR_NAME));
try { try {
Files.createDirectories(cordappsDir); Files.createDirectories(cordappsDir);
} catch (IOException ex) { } catch (IOException ex) {

View File

@ -86,7 +86,7 @@ class StandaloneCordaRPClientTest {
} }
private fun copyFinanceCordapp() { private fun copyFinanceCordapp() {
val cordappsDir = (factory.baseDirectory(notaryConfig) / "cordapps").createDirectories() val cordappsDir = (factory.baseDirectory(notaryConfig) / NodeProcess.CORDAPPS_DIR_NAME).createDirectories()
// Find the finance jar file for the smoke tests of this module // Find the finance jar file for the smoke tests of this module
val financeJar = Paths.get("build", "resources", "smokeTest").list { val financeJar = Paths.get("build", "resources", "smokeTest").list {
it.filter { "corda-finance" in it.toString() }.toList().single() it.filter { "corda-finance" in it.toString() }.toList().single()

View File

@ -1,5 +1,6 @@
gradlePluginsVersion=2.0.9 gradlePluginsVersion=2.0.9
kotlinVersion=1.1.60 kotlinVersion=1.1.60
platformVersion=2
guavaVersion=21.0 guavaVersion=21.0
bouncycastleVersion=1.57 bouncycastleVersion=1.57
typesafeConfigVersion=1.3.1 typesafeConfigVersion=1.3.1

View File

@ -0,0 +1,75 @@
package net.corda.core
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.*
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.nodeapi.User
import net.corda.smoketesting.NodeConfig
import net.corda.smoketesting.NodeProcess
import net.corda.testing.common.internal.ProjectStructure
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import java.nio.file.Paths
import java.util.*
import java.util.concurrent.atomic.AtomicInteger
import java.util.jar.JarFile
import kotlin.streams.toList
class NodeVersioningTest {
private companion object {
val user = User("user1", "test", permissions = setOf("ALL"))
val port = AtomicInteger(15100)
val expectedPlatformVersion = (ProjectStructure.projectRootDir / "constants.properties").read {
val constants = Properties()
constants.load(it)
constants.getProperty("platformVersion").toInt()
}
}
private val factory = NodeProcess.Factory()
private val aliceConfig = NodeConfig(
legalName = CordaX500Name(organisation = "Alice Corp", locality = "Madrid", country = "ES"),
p2pPort = port.andIncrement,
rpcPort = port.andIncrement,
webPort = port.andIncrement,
isNotary = false,
users = listOf(user)
)
@Test
fun `platform version in manifest file`() {
val manifest = JarFile(factory.cordaJar.toFile()).manifest
assertThat(manifest.mainAttributes.getValue("Corda-Platform-Version").toInt()).isEqualTo(expectedPlatformVersion)
}
@Test
fun `platform version from RPC`() {
val cordappsDir = (factory.baseDirectory(aliceConfig) / NodeProcess.CORDAPPS_DIR_NAME).createDirectories()
// Find the jar file for the smoke tests of this module
val selfCordapp = Paths.get("build", "libs").list {
it.filter { "-smokeTests" in it.toString() }.toList().single()
}
selfCordapp.copyToDirectory(cordappsDir)
factory.create(aliceConfig).use { alice ->
alice.connect().use {
val rpc = it.proxy
assertThat(rpc.protocolVersion).isEqualTo(expectedPlatformVersion)
assertThat(rpc.nodeInfo().platformVersion).isEqualTo(expectedPlatformVersion)
assertThat(rpc.startFlow(NodeVersioningTest::GetPlatformVersionFlow).returnValue.getOrThrow()).isEqualTo(expectedPlatformVersion)
}
}
}
@StartableByRPC
class GetPlatformVersionFlow : FlowLogic<Int>() {
@Suspendable
override fun call(): Int = serviceHub.myInfo.platformVersion
}
}

View File

@ -14,6 +14,7 @@ import net.corda.core.utilities.unwrap
import net.corda.nodeapi.User import net.corda.nodeapi.User
import net.corda.smoketesting.NodeConfig import net.corda.smoketesting.NodeConfig
import net.corda.smoketesting.NodeProcess import net.corda.smoketesting.NodeProcess
import net.corda.smoketesting.NodeProcess.Companion.CORDAPPS_DIR_NAME
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Test import org.junit.Test
import java.nio.file.Paths import java.nio.file.Paths
@ -22,7 +23,6 @@ import kotlin.streams.toList
class CordappSmokeTest { class CordappSmokeTest {
private companion object { private companion object {
private const val CORDAPPS_DIR_NAME = "cordapps"
val user = User("user1", "test", permissions = setOf("ALL")) val user = User("user1", "test", permissions = setOf("ALL"))
val port = AtomicInteger(15100) val port = AtomicInteger(15100)
} }
@ -38,7 +38,6 @@ class CordappSmokeTest {
users = listOf(user) users = listOf(user)
) )
@Test @Test
fun `FlowContent appName returns the filename of the CorDapp jar`() { fun `FlowContent appName returns the filename of the CorDapp jar`() {
val cordappsDir = (factory.baseDirectory(aliceConfig) / CORDAPPS_DIR_NAME).createDirectories() val cordappsDir = (factory.baseDirectory(aliceConfig) / CORDAPPS_DIR_NAME).createDirectories()

View File

@ -10,7 +10,7 @@ import net.corda.node.internal.NodeStartup
import net.corda.node.services.Permissions.Companion.startFlow import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.nodeapi.User import net.corda.nodeapi.User
import net.corda.testing.ALICE import net.corda.testing.ALICE
import net.corda.testing.ProjectStructure.projectRootDir import net.corda.testing.common.internal.ProjectStructure.projectRootDir
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy import org.assertj.core.api.Assertions.assertThatThrownBy

View File

@ -190,11 +190,13 @@ fun <M : Any> MessagingService.onNext(topic: String, sessionId: Long): CordaFutu
return messageFuture return messageFuture
} }
fun MessagingService.send(topic: String, sessionID: Long, payload: Any, to: MessageRecipients, uuid: UUID = UUID.randomUUID()) fun MessagingService.send(topic: String, sessionID: Long, payload: Any, to: MessageRecipients, uuid: UUID = UUID.randomUUID()) {
= send(TopicSession(topic, sessionID), payload, to, uuid) send(TopicSession(topic, sessionID), payload, to, uuid)
}
fun MessagingService.send(topicSession: TopicSession, payload: Any, to: MessageRecipients, uuid: UUID = UUID.randomUUID(), retryId: Long? = null) fun MessagingService.send(topicSession: TopicSession, payload: Any, to: MessageRecipients, uuid: UUID = UUID.randomUUID(), retryId: Long? = null) {
= send(createMessage(topicSession, payload.serialize().bytes, uuid), to, retryId) send(createMessage(topicSession, payload.serialize().bytes, uuid), to, retryId)
}
interface MessageHandlerRegistration interface MessageHandlerRegistration

View File

@ -1,9 +1,6 @@
package net.corda.node.services.messaging package net.corda.node.services.messaging
import net.corda.core.concurrent.CordaFuture
import net.corda.core.crypto.generateKeyPair import net.corda.core.crypto.generateKeyPair
import net.corda.core.internal.concurrent.doneFuture
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.services.RPCUserService import net.corda.node.services.RPCUserService
import net.corda.node.services.RPCUserServiceImpl import net.corda.node.services.RPCUserServiceImpl
@ -27,13 +24,13 @@ import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.TemporaryFolder import org.junit.rules.TemporaryFolder
import java.net.ServerSocket import java.net.ServerSocket
import java.util.concurrent.BlockingQueue
import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.MILLISECONDS
import kotlin.concurrent.thread import kotlin.concurrent.thread
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNull import kotlin.test.assertNull
//TODO This needs to be merged into P2PMessagingTest as that creates a more realistic environment
class ArtemisMessagingTests { class ArtemisMessagingTests {
companion object { companion object {
const val TOPIC = "platform.self" const val TOPIC = "platform.self"
@ -54,21 +51,19 @@ class ArtemisMessagingTests {
private lateinit var config: NodeConfiguration private lateinit var config: NodeConfiguration
private lateinit var database: CordaPersistence private lateinit var database: CordaPersistence
private lateinit var userService: RPCUserService private lateinit var userService: RPCUserService
private lateinit var networkMapRegistrationFuture: CordaFuture<Unit>
private var messagingClient: P2PMessagingClient? = null private var messagingClient: P2PMessagingClient? = null
private var messagingServer: ArtemisMessagingServer? = null private var messagingServer: ArtemisMessagingServer? = null
private lateinit var networkMapCache: NetworkMapCacheImpl private lateinit var networkMapCache: NetworkMapCacheImpl
@Before @Before
fun setUp() { fun setUp() {
val baseDirectory = temporaryFolder.root.toPath()
userService = RPCUserServiceImpl(emptyList()) userService = RPCUserServiceImpl(emptyList())
config = testNodeConfiguration( config = testNodeConfiguration(
baseDirectory = baseDirectory, baseDirectory = temporaryFolder.root.toPath(),
myLegalName = ALICE.name) myLegalName = ALICE.name)
LogHelper.setLevel(PersistentUniquenessProvider::class) LogHelper.setLevel(PersistentUniquenessProvider::class)
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock()) database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock())
networkMapRegistrationFuture = doneFuture(Unit)
networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database), rigorousMock()) networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database), rigorousMock())
} }
@ -76,8 +71,6 @@ class ArtemisMessagingTests {
fun cleanUp() { fun cleanUp() {
messagingClient?.stop() messagingClient?.stop()
messagingServer?.stop() messagingServer?.stop()
messagingClient = null
messagingServer = null
database.close() database.close()
LogHelper.reset(PersistentUniquenessProvider::class) LogHelper.reset(PersistentUniquenessProvider::class)
} }
@ -120,9 +113,7 @@ class ArtemisMessagingTests {
@Test @Test
fun `client should be able to send message to itself`() { fun `client should be able to send message to itself`() {
val receivedMessages = LinkedBlockingQueue<Message>() val (messagingClient, receivedMessages) = createAndStartClientAndServer()
val messagingClient = createAndStartClientAndServer(receivedMessages)
val message = messagingClient.createMessage(TOPIC, data = "first msg".toByteArray()) val message = messagingClient.createMessage(TOPIC, data = "first msg".toByteArray())
messagingClient.send(message, messagingClient.myAddress) messagingClient.send(message, messagingClient.myAddress)
@ -132,76 +123,45 @@ class ArtemisMessagingTests {
} }
@Test @Test
fun `client should be able to send message to itself before network map is available, and receive after`() { fun `platform version is included in the message`() {
val settableFuture = openFuture<Unit>() val (messagingClient, receivedMessages) = createAndStartClientAndServer(platformVersion = 3)
networkMapRegistrationFuture = settableFuture
val receivedMessages = LinkedBlockingQueue<Message>()
val messagingClient = createAndStartClientAndServer(receivedMessages)
val message = messagingClient.createMessage(TOPIC, data = "first msg".toByteArray()) val message = messagingClient.createMessage(TOPIC, data = "first msg".toByteArray())
messagingClient.send(message, messagingClient.myAddress) messagingClient.send(message, messagingClient.myAddress)
settableFuture.set(Unit) val received = receivedMessages.take()
val firstActual: Message = receivedMessages.take() assertThat(received.platformVersion).isEqualTo(3)
assertEquals("first msg", String(firstActual.data))
assertNull(receivedMessages.poll(200, MILLISECONDS))
}
@Test
fun `client should be able to send large numbers of messages to itself before network map is available and survive restart, then receive messages`() {
// Crank the iteration up as high as you want... just takes longer to run.
val iterations = 100
networkMapRegistrationFuture = openFuture()
val receivedMessages = LinkedBlockingQueue<Message>()
val messagingClient = createAndStartClientAndServer(receivedMessages)
for (iter in 1..iterations) {
val message = messagingClient.createMessage(TOPIC, data = "first msg $iter".toByteArray())
messagingClient.send(message, messagingClient.myAddress)
}
// Stop client and server and create afresh.
messagingClient.stop()
messagingServer?.stop()
networkMapRegistrationFuture = doneFuture(Unit)
createAndStartClientAndServer(receivedMessages)
for (iter in 1..iterations) {
val firstActual: Message = receivedMessages.take()
assertThat(String(firstActual.data)).isEqualTo("first msg $iter")
}
assertNull(receivedMessages.poll(200, MILLISECONDS))
} }
private fun startNodeMessagingClient() { private fun startNodeMessagingClient() {
messagingClient!!.start() messagingClient!!.start()
} }
private fun createAndStartClientAndServer(receivedMessages: LinkedBlockingQueue<Message>): P2PMessagingClient { private fun createAndStartClientAndServer(platformVersion: Int = 1): Pair<P2PMessagingClient, BlockingQueue<ReceivedMessage>> {
val receivedMessages = LinkedBlockingQueue<ReceivedMessage>()
createMessagingServer().start() createMessagingServer().start()
val messagingClient = createMessagingClient() val messagingClient = createMessagingClient(platformVersion = platformVersion)
startNodeMessagingClient() startNodeMessagingClient()
messagingClient.addMessageHandler(TOPIC) { message, _ -> messagingClient.addMessageHandler(TOPIC) { message, _ ->
receivedMessages.add(message) receivedMessages.add(message)
} }
// Run after the handlers are added, otherwise (some of) the messages get delivered and discarded / dead-lettered. // Run after the handlers are added, otherwise (some of) the messages get delivered and discarded / dead-lettered.
thread { messagingClient.run() } thread(isDaemon = true) { messagingClient.run() }
return messagingClient
return Pair(messagingClient, receivedMessages)
} }
private fun createMessagingClient(server: NetworkHostAndPort = NetworkHostAndPort("localhost", serverPort)): P2PMessagingClient { private fun createMessagingClient(server: NetworkHostAndPort = NetworkHostAndPort("localhost", serverPort), platformVersion: Int = 1): P2PMessagingClient {
return database.transaction { return database.transaction {
P2PMessagingClient( P2PMessagingClient(
config, config,
MOCK_VERSION_INFO, MOCK_VERSION_INFO.copy(platformVersion = platformVersion),
server, server,
identity.public, identity.public,
ServiceAffinityExecutor("ArtemisMessagingTests", 1), ServiceAffinityExecutor("ArtemisMessagingTests", 1),
database).apply { database
).apply {
config.configureWithDevSSLCertificate() config.configureWithDevSSLCertificate()
messagingClient = this messagingClient = this
} }

View File

@ -9,7 +9,7 @@ import net.corda.node.internal.NodeStartup
import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_BANK_A
import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.DUMMY_REGULATOR import net.corda.testing.DUMMY_REGULATOR
import net.corda.testing.ProjectStructure.projectRootDir import net.corda.testing.common.internal.ProjectStructure.projectRootDir
import net.corda.testing.node.NotarySpec import net.corda.testing.node.NotarySpec
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Test import org.junit.Test

View File

@ -21,7 +21,8 @@ class NodeProcess(
private val node: Process, private val node: Process,
private val client: CordaRPCClient private val client: CordaRPCClient
) : AutoCloseable { ) : AutoCloseable {
private companion object { companion object {
const val CORDAPPS_DIR_NAME = "cordapps"
private val log = contextLogger() private val log = contextLogger()
} }
@ -42,9 +43,11 @@ class NodeProcess(
(nodeDir / "artemis").toFile().deleteRecursively() (nodeDir / "artemis").toFile().deleteRecursively()
} }
// TODO All use of this factory have duplicate code which is either bundling the calling module or a 3rd party module
// as a CorDapp for the nodes.
class Factory( class Factory(
private val buildDirectory: Path = Paths.get("build"), val buildDirectory: Path = Paths.get("build"),
private val cordaJar: Path = Paths.get(this::class.java.getResource("/corda.jar").toURI()) val cordaJar: Path = Paths.get(this::class.java.getResource("/corda.jar").toURI())
) { ) {
private companion object { private companion object {
val javaPath: Path = Paths.get(System.getProperty("java.home"), "bin", "java") val javaPath: Path = Paths.get(System.getProperty("java.home"), "bin", "java")
@ -71,36 +74,37 @@ class NodeProcess(
val process = startNode(nodeDir) val process = startNode(nodeDir)
val client = CordaRPCClient(NetworkHostAndPort("localhost", config.rpcPort)) val client = CordaRPCClient(NetworkHostAndPort("localhost", config.rpcPort))
val user = config.users[0] waitForNode(process, config, client)
return NodeProcess(config, nodeDir, process, client)
}
val setupExecutor = Executors.newSingleThreadScheduledExecutor() private fun waitForNode(process: Process, config: NodeConfig, client: CordaRPCClient) {
val executor = Executors.newSingleThreadScheduledExecutor()
try { try {
setupExecutor.scheduleWithFixedDelay({ executor.scheduleWithFixedDelay({
try { try {
if (!process.isAlive) { if (!process.isAlive) {
log.error("Node '${config.commonName}' has died.") log.error("Node '${config.commonName}' has died.")
return@scheduleWithFixedDelay return@scheduleWithFixedDelay
} }
val conn = client.start(user.username, user.password) val rpcConnection = config.users[0].let { client.start(it.username, it.password) }
conn.close() rpcConnection.close()
// Cancel the "setup" task now that we've created the RPC client. // Cancel the "setup" task now that we've created the RPC client.
setupExecutor.shutdown() executor.shutdown()
} catch (e: Exception) { } catch (e: Exception) {
log.warn("Node '{}' not ready yet (Error: {})", config.commonName, e.message) log.warn("Node '{}' not ready yet (Error: {})", config.commonName, e.message)
} }
}, 5, 1, SECONDS) }, 5, 1, SECONDS)
val setupOK = setupExecutor.awaitTermination(120, SECONDS) val setupOK = executor.awaitTermination(120, SECONDS)
check(setupOK && process.isAlive) { "Failed to create RPC connection" } check(setupOK && process.isAlive) { "Failed to create RPC connection" }
} catch (e: Exception) { } catch (e: Exception) {
process.destroyForcibly() process.destroyForcibly()
throw e throw e
} finally { } finally {
setupExecutor.shutdownNow() executor.shutdownNow()
} }
return NodeProcess(config, nodeDir, process, client)
} }
private fun startNode(nodeDir: Path): Process { private fun startNode(nodeDir: Path): Process {

View File

@ -1,4 +1,4 @@
package net.corda.testing package net.corda.testing.common.internal
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.core.internal.isDirectory import net.corda.core.internal.isDirectory