Ability to run integration tests against standalone databases

* Additional database confing and implied property ${nodeOrganizationName}.
* Integration tests extend from base class which allows to configure database connection (in-memory/remote db) and to run setup/tear down SQL scripts.
This commit is contained in:
szymonsztuka 2017-11-13 17:29:57 +00:00 committed by GitHub
parent 523a6db0b9
commit 359610ff14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 212 additions and 50 deletions

View File

@ -48,6 +48,7 @@ buildscript {
ext.dependency_checker_version = '3.0.1' ext.dependency_checker_version = '3.0.1'
ext.commons_collections_version = '4.1' ext.commons_collections_version = '4.1'
ext.beanutils_version = '1.9.3' ext.beanutils_version = '1.9.3'
ext.spring_jdbc_version ='5.0.0.RELEASE'
// Update 121 is required for ObjectInputFilter and at time of writing 131 was latest: // Update 121 is required for ObjectInputFilter and at time of writing 131 was latest:
ext.java8_minUpdateVersion = '131' ext.java8_minUpdateVersion = '131'
@ -150,13 +151,20 @@ allprojects {
final AMQP_ENABLE_PROP_NAME = "net.corda.testing.amqp.enable" final AMQP_ENABLE_PROP_NAME = "net.corda.testing.amqp.enable"
systemProperty(AMQP_ENABLE_PROP_NAME, System.getProperty(AMQP_ENABLE_PROP_NAME)) systemProperty(AMQP_ENABLE_PROP_NAME, System.getProperty(AMQP_ENABLE_PROP_NAME))
//Allows to pass database-related properties for unit and integration tests // relational database provider to be used by node
final DATABASE_PROVIDER = "databaseProvider"
final DATASOURCE_URL = "dataSourceProperties.dataSource.url" final DATASOURCE_URL = "dataSourceProperties.dataSource.url"
final DATASOURCE_CLASSNAME = "dataSourceProperties.dataSourceClassName" final DATASOURCE_CLASSNAME = "dataSourceProperties.dataSourceClassName"
final DATASOURCE_USER = "dataSourceProperties.dataSource.user" final DATASOURCE_USER = "dataSourceProperties.dataSource.user"
final DATASOURCE_PASSWORD = "dataSourceProperties.dataSource.password" final DATASOURCE_PASSWORD = "dataSourceProperties.dataSource.password"
[DATASOURCE_URL, DATASOURCE_CLASSNAME, DATASOURCE_USER, DATASOURCE_PASSWORD].forEach { // integration testing database configuration (to be used in conjunction with a DATABASE_PROVIDER)
final TEST_DB_ADMIN_USER = "test.db.admin.user"
final TEST_DB_ADMIN_PASSWORD = "test.db.admin.password"
final TEST_DB_SCRIPT_DIR = "test.db.script.dir"
[DATABASE_PROVIDER,DATASOURCE_URL, DATASOURCE_CLASSNAME, DATASOURCE_USER, DATASOURCE_PASSWORD,
TEST_DB_ADMIN_USER, TEST_DB_ADMIN_PASSWORD, TEST_DB_SCRIPT_DIR].forEach {
def property = System.getProperty(it) def property = System.getProperty(it)
if (property != null) { if (property != null) {
systemProperty(it, property) systemProperty(it, property)

View File

@ -19,12 +19,12 @@ import net.corda.testing.driver.driver
import org.junit.Test import org.junit.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
class IntegrationTestingTutorial { class IntegrationTestingTutorial : IntegrationTest() {
@Test @Test
fun `alice bob cash exchange example`() { fun `alice bob cash exchange example`() {
// START 1 // START 1
driver(startNodesInProcess = true, driver(startNodesInProcess = true,
extraCordappPackagesToScan = listOf("net.corda.finance.contracts.asset")) { extraCordappPackagesToScan = listOf("net.corda.finance.contracts.asset","net.corda.finance.schemas")) {
val aliceUser = User("aliceUser", "testPassword1", permissions = setOf( val aliceUser = User("aliceUser", "testPassword1", permissions = setOf(
startFlow<CashIssueFlow>(), startFlow<CashIssueFlow>(),
startFlow<CashPaymentFlow>(), startFlow<CashPaymentFlow>(),

View File

@ -43,16 +43,24 @@ TODO: Add instructions on manual testing
External Database Testing External Database Testing
------------------------- -------------------------
By default, all tests which need a database, utilize built-in H2 instances. In case the testing with other database backends By default, all tests which need a database, utilize built-in H2 instances. For the purpose of testing other relational
or other database setup (H2 in server mode for example), while running tests extra parameters can be used to specify required database providers or different database setups (for example, H2 in server mode), we introduce an optional system
properties. Appropriate changes will then be applied to all tests in a appropriate manner. property called ``databaseProvider`` which is resolved at run-time to load a configuration file on the classpath with the
name ``$databaseProvider.conf`` containing database configuration properties defined in the general node configuration
file (see ``reference.conf``). These will be used to override the default H2 settings defined in ``reference.conf``.
Appropriate changes will then be applied to all tests in a appropriate manner.
- ``dataSourceProperties.dataSource.url`` - JDBC datasource URL. Appropriate drivers must be available in classpath. Also, for - ``dataSourceProperties.dataSource.url`` - JDBC datasource URL. Appropriate drivers must be available in classpath. Also, for
different tests random database name is appended at the end of this string, ie. ``jdbc:h2:tcp://localhost:9092`` will become different tests random database name is appended at the end of this string, ie. ``jdbc:h2:tcp://localhost:9092`` will become
full, proper URL - ie.``jdbc:h2:tcp://localhost:9092/34jh543gk243g2`` - mind the last slash missing. full, proper URL - ie.``jdbc:h2:tcp://localhost:9092/34jh543gk243g2`` - mind the last slash missing.
- ``dataSourceProperties.dataSourceClassName`` - JDBC driver classname - defaults to ``org.h2.jdbcx.JdbcDataSource``) - ``dataSourceProperties.dataSourceClassName`` - JDBC driver classname
- ``dataSourceProperties.dataSource.user`` - JDBC username - defaults to ``sa`` - ``dataSourceProperties.dataSource.user`` - JDBC username
- ``dataSourceProperties.dataSource.password`` - JDBC password - defaults to ``""`` (Empty string) - ``dataSourceProperties.dataSource.password`` - JDBC password
- ``database.transactionIsolationLevel`` - Isolation level pertinent to relevant database provider
All defaults are taken from the ``reference.conf`` file.

View File

@ -4,11 +4,12 @@ import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.finance.EUR import net.corda.finance.EUR
import net.corda.finance.USD import net.corda.finance.USD
import net.corda.testing.IntegrationTest
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.junit.Test import org.junit.Test
class CashConfigDataFlowTest { class CashConfigDataFlowTest : IntegrationTest() {
@Test @Test
fun `issuable currencies are read in from node config`() { fun `issuable currencies are read in from node config`() {
driver { driver {

View File

@ -10,6 +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.IntegrationTest
import net.corda.testing.ProjectStructure.projectRootDir import net.corda.testing.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
@ -19,7 +20,7 @@ import java.io.*
import java.nio.file.Files import java.nio.file.Files
import kotlin.test.assertEquals import kotlin.test.assertEquals
class BootTests { class BootTests : IntegrationTest() {
@Test @Test
fun `java deserialization is disabled`() { fun `java deserialization is disabled`() {

View File

@ -11,12 +11,13 @@ 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.BOB import net.corda.testing.BOB
import net.corda.testing.IntegrationTest
import net.corda.testing.chooseIdentity import net.corda.testing.chooseIdentity
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.junit.Test import org.junit.Test
class CordappScanningDriverTest { class CordappScanningDriverTest : IntegrationTest() {
@Test @Test
fun `sub-classed initiated flow pointing to the same initiating flow as its super-class`() { fun `sub-classed initiated flow pointing to the same initiating flow as its super-class`() {
val user = User("u", "p", setOf(startFlow<ReceiveFlow>())) val user = User("u", "p", setOf(startFlow<ReceiveFlow>()))

View File

@ -15,6 +15,7 @@ import net.corda.finance.flows.CashPaymentFlow
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.DUMMY_NOTARY import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.IntegrationTest
import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
import net.corda.testing.node.NotarySpec import net.corda.testing.node.NotarySpec
@ -38,7 +39,7 @@ private fun checkQuasarAgent() {
} }
@Ignore("Run these locally") @Ignore("Run these locally")
class NodePerformanceTests { class NodePerformanceTests : IntegrationTest() {
@StartableByRPC @StartableByRPC
class EmptyFlow : FlowLogic<Unit>() { class EmptyFlow : FlowLogic<Unit>() {
@Suspendable @Suspendable
@ -54,6 +55,7 @@ class NodePerformanceTests {
@Before @Before
fun before() { fun before() {
checkQuasarAgent() checkQuasarAgent()
super.setUp()
} }
@Test @Test

View File

@ -1,6 +1,7 @@
package net.corda.node package net.corda.node
import com.google.common.base.Stopwatch import com.google.common.base.Stopwatch
import net.corda.testing.IntegrationTest
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
import org.junit.Ignore import org.junit.Ignore
import org.junit.Test import org.junit.Test
@ -8,7 +9,7 @@ import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@Ignore("Only use locally") @Ignore("Only use locally")
class NodeStartupPerformanceTests { class NodeStartupPerformanceTests : IntegrationTest() {
// Measure the startup time of nodes. Note that this includes an RPC roundtrip, which causes e.g. Kryo initialisation. // Measure the startup time of nodes. Note that this includes an RPC roundtrip, which causes e.g. Kryo initialisation.
@Test @Test

View File

@ -20,6 +20,7 @@ import net.corda.node.internal.cordapp.CordappProviderImpl
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.SerializationEnvironmentRule import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.IntegrationTest
import net.corda.testing.driver.DriverDSLExposedInterface import net.corda.testing.driver.DriverDSLExposedInterface
import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
@ -32,7 +33,7 @@ import java.net.URLClassLoader
import java.nio.file.Files import java.nio.file.Files
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
class AttachmentLoadingTests { class AttachmentLoadingTests : IntegrationTest() {
@Rule @Rule
@JvmField @JvmField
val testSerialization = SerializationEnvironmentRule() val testSerialization = SerializationEnvironmentRule()
@ -79,6 +80,7 @@ class AttachmentLoadingTests {
@Before @Before
fun setup() { fun setup() {
super.setUp()
services = Services() services = Services()
} }

View File

@ -35,16 +35,22 @@ import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNetwork.MockNode import net.corda.testing.node.MockNetwork.MockNode
import net.corda.testing.node.MockNodeParameters import net.corda.testing.node.MockNodeParameters
import org.junit.After import org.junit.After
import org.junit.Before
import org.junit.Test import org.junit.Test
import java.nio.file.Paths import java.nio.file.Paths
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertTrue import kotlin.test.assertTrue
class BFTNotaryServiceTests { class BFTNotaryServiceTests {
private val mockNet = MockNetwork() private lateinit var mockNet: MockNetwork
private lateinit var notary: Party private lateinit var notary: Party
private lateinit var node: StartedNode<MockNode> private lateinit var node: StartedNode<MockNode>
@Before
fun before() {
mockNet = MockNetwork()
node = mockNet.createNode()
}
@After @After
fun stopNodes() { fun stopNodes() {
mockNet.stopNodes() mockNet.stopNodes()

View File

@ -24,7 +24,7 @@ import org.junit.Test
import rx.Observable import rx.Observable
import java.util.* import java.util.*
class DistributedServiceTests { class DistributedServiceTests : IntegrationTest() {
private lateinit var alice: NodeHandle private lateinit var alice: NodeHandle
private lateinit var notaryNodes: List<NodeHandle.OutOfProcess> private lateinit var notaryNodes: List<NodeHandle.OutOfProcess>
private lateinit var aliceProxy: CordaRPCOps private lateinit var aliceProxy: CordaRPCOps

View File

@ -13,6 +13,7 @@ import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.StartedNode import net.corda.node.internal.StartedNode
import net.corda.node.services.transactions.RaftValidatingNotaryService import net.corda.node.services.transactions.RaftValidatingNotaryService
import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_BANK_A
import net.corda.testing.IntegrationTest
import net.corda.testing.chooseIdentity import net.corda.testing.chooseIdentity
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.NodeHandle
@ -25,7 +26,7 @@ import java.util.*
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
class RaftNotaryServiceTests { class RaftNotaryServiceTests : IntegrationTest() {
private val notaryName = CordaX500Name(RaftValidatingNotaryService.id, "RAFT Notary Service", "London", "GB") private val notaryName = CordaX500Name(RaftValidatingNotaryService.id, "RAFT Notary Service", "London", "GB")
@Test @Test

View File

@ -17,6 +17,7 @@ import net.corda.node.internal.StartedNode
import net.corda.node.services.messaging.* import net.corda.node.services.messaging.*
import net.corda.node.services.transactions.RaftValidatingNotaryService import net.corda.node.services.transactions.RaftValidatingNotaryService
import net.corda.testing.ALICE import net.corda.testing.ALICE
import net.corda.testing.IntegrationTest
import net.corda.testing.chooseIdentity import net.corda.testing.chooseIdentity
import net.corda.testing.driver.DriverDSLExposedInterface import net.corda.testing.driver.DriverDSLExposedInterface
import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.NodeHandle
@ -30,7 +31,7 @@ import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
class P2PMessagingTest { class P2PMessagingTest : IntegrationTest() {
private companion object { private companion object {
val DISTRIBUTED_SERVICE_NAME = CordaX500Name(RaftValidatingNotaryService.id, "DistributedService", "London", "GB") val DISTRIBUTED_SERVICE_NAME = CordaX500Name(RaftValidatingNotaryService.id, "DistributedService", "London", "GB")
} }

View File

@ -20,6 +20,7 @@ import net.corda.core.utilities.getOrThrow
import net.corda.node.services.Permissions.Companion.invokeRpc import net.corda.node.services.Permissions.Companion.invokeRpc
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.IntegrationTest
import net.corda.testing.chooseIdentity import net.corda.testing.chooseIdentity
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
import org.junit.Assume.assumeFalse import org.junit.Assume.assumeFalse
@ -31,7 +32,7 @@ import javax.persistence.Table
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
class NodeStatePersistenceTests { class NodeStatePersistenceTests : IntegrationTest() {
@Test @Test
fun `persistent state survives node restart`() { fun `persistent state survives node restart`() {
// Temporary disable this test when executed on Windows. It is known to be sporadically failing. // Temporary disable this test when executed on Windows. It is known to be sporadically failing.

View File

@ -8,6 +8,7 @@ import net.corda.core.internal.*
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.node.utilities.* import net.corda.node.utilities.*
import net.corda.nodeapi.config.SSLConfiguration import net.corda.nodeapi.config.SSLConfiguration
import net.corda.nodeapi.config.toProperties
import org.bouncycastle.asn1.x509.GeneralName import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralSubtree import org.bouncycastle.asn1.x509.GeneralSubtree
import org.bouncycastle.asn1.x509.NameConstraints import org.bouncycastle.asn1.x509.NameConstraints
@ -27,10 +28,25 @@ object ConfigHelper {
val parseOptions = ConfigParseOptions.defaults() val parseOptions = ConfigParseOptions.defaults()
val defaultConfig = ConfigFactory.parseResources("reference.conf", parseOptions.setAllowMissing(false)) val defaultConfig = ConfigFactory.parseResources("reference.conf", parseOptions.setAllowMissing(false))
val appConfig = ConfigFactory.parseFile(configFile.toFile(), parseOptions.setAllowMissing(allowMissingConfig)) val appConfig = ConfigFactory.parseFile(configFile.toFile(), parseOptions.setAllowMissing(allowMissingConfig))
val databaseConfig = ConfigFactory.parseResources(System.getProperty("databaseProvider")+".conf", parseOptions.setAllowMissing(true))
//typesafe workaround: a system property with placeholder is passed as value (inside quotes),
//undo adding the quotes for a fixed placeholder ${nodeOrganizationName}
//https://github.com/lightbend/config/issues/265
var systemUnquotedPlaceholders: Config = ConfigFactory.empty()
ConfigFactory.systemProperties().toProperties().forEach { name, value ->
if (value.toString().contains("\${nodeOrganizationName}")) {
var unquotedPlaceholder = "\"" + value.toString().replace("\${nodeOrganizationName}","\"\${nodeOrganizationName}\"") + "\""
systemUnquotedPlaceholders = systemUnquotedPlaceholders.withFallback(ConfigFactory.parseString(name.toString() + " = " + unquotedPlaceholder))
}
}
val finalConfig = configOverrides val finalConfig = configOverrides
// Add substitution values here // Add substitution values here
.withFallback(systemUnquotedPlaceholders)
.withFallback(configOf("nodeOrganizationName" to baseDirectory.fileName.toString().replace(" ","").replace("-","_")))
.withFallback(ConfigFactory.systemProperties()) .withFallback(ConfigFactory.systemProperties())
.withFallback( configOf("baseDirectory" to baseDirectory.toString())) .withFallback( configOf("baseDirectory" to baseDirectory.toString()))
.withFallback(databaseConfig)
.withFallback(appConfig) .withFallback(appConfig)
.withFallback(defaultConfig) .withFallback(defaultConfig)
.resolve() .resolve()

View File

@ -89,7 +89,7 @@ class DistributedImmutableMapTests {
private fun createReplica(myAddress: NetworkHostAndPort, clusterAddress: NetworkHostAndPort? = null): CompletableFuture<Member> { private fun createReplica(myAddress: NetworkHostAndPort, clusterAddress: NetworkHostAndPort? = null): CompletableFuture<Member> {
val storage = Storage.builder().withStorageLevel(StorageLevel.MEMORY).build() val storage = Storage.builder().withStorageLevel(StorageLevel.MEMORY).build()
val address = Address(myAddress.host, myAddress.port) val address = Address(myAddress.host, myAddress.port)
val database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties("serverNameTablePrefix", "PORT_${myAddress.port}_"), ::makeTestIdentityService) val database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService)
databases.add(database) databases.add(database)
val stateMachineFactory = { DistributedImmutableMap(database, RaftUniquenessProvider.Companion::createMap) } val stateMachineFactory = { DistributedImmutableMap(database, RaftUniquenessProvider.Companion::createMap) }

View File

@ -7,12 +7,13 @@ import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.nodeapi.User import net.corda.nodeapi.User
import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_BANK_A
import net.corda.testing.DUMMY_BANK_B import net.corda.testing.DUMMY_BANK_B
import net.corda.testing.IntegrationTest
import net.corda.testing.driver.PortAllocation import net.corda.testing.driver.PortAllocation
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
import org.junit.Test import org.junit.Test
import java.util.concurrent.CompletableFuture.supplyAsync import java.util.concurrent.CompletableFuture.supplyAsync
class AttachmentDemoTest { class AttachmentDemoTest : IntegrationTest() {
// run with a 10,000,000 bytes in-memory zip file. In practice, a slightly bigger file will be used (~10,002,000 bytes). // run with a 10,000,000 bytes in-memory zip file. In practice, a slightly bigger file will be used (~10,002,000 bytes).
// Force INFO logging to prevent printing 10MB arrays in logfiles // Force INFO logging to prevent printing 10MB arrays in logfiles
@Test @Test

View File

@ -4,11 +4,12 @@ import net.corda.bank.api.BankOfCordaClientApi
import net.corda.bank.api.BankOfCordaWebApi.IssueRequestParams import net.corda.bank.api.BankOfCordaWebApi.IssueRequestParams
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.testing.BOC import net.corda.testing.BOC
import net.corda.testing.IntegrationTest
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
import org.junit.Test import org.junit.Test
import kotlin.test.assertTrue import kotlin.test.assertTrue
class BankOfCordaHttpAPITest { class BankOfCordaHttpAPITest : IntegrationTest() {
@Test @Test
fun `issuer flow via Http`() { fun `issuer flow via Http`() {
driver(extraCordappPackagesToScan = listOf("net.corda.finance"), isDebug = true) { driver(extraCordappPackagesToScan = listOf("net.corda.finance"), isDebug = true) {

View File

@ -15,7 +15,7 @@ import net.corda.testing.*
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
import org.junit.Test import org.junit.Test
class BankOfCordaRPCClientTest { class BankOfCordaRPCClientTest : IntegrationTest() {
@Test @Test
fun `issuer flow via RPC`() { fun `issuer flow via RPC`() {
val commonPermissions = setOf( val commonPermissions = setOf(

View File

@ -33,7 +33,7 @@ import rx.Observable
import java.time.Duration import java.time.Duration
import java.time.LocalDate import java.time.LocalDate
class IRSDemoTest { class IRSDemoTest : IntegrationTest() {
companion object { companion object {
val log = loggerFor<IRSDemoTest>() val log = loggerFor<IRSDemoTest>()
} }

View File

@ -5,6 +5,7 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_BANK_A
import net.corda.testing.DUMMY_BANK_B import net.corda.testing.DUMMY_BANK_B
import net.corda.testing.IntegrationTest
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
import net.corda.testing.http.HttpApi import net.corda.testing.http.HttpApi
import net.corda.vega.api.PortfolioApi import net.corda.vega.api.PortfolioApi
@ -16,7 +17,7 @@ import org.junit.Test
import java.math.BigDecimal import java.math.BigDecimal
import java.time.LocalDate import java.time.LocalDate
class SimmValuationTest { class SimmValuationTest : IntegrationTest() {
private companion object { private companion object {
// SIMM demo can only currently handle one valuation date due to a lack of market data or a market data source. // SIMM demo can only currently handle one valuation date due to a lack of market data or a market data source.
val valuationDate: LocalDate = LocalDate.parse("2016-06-06") val valuationDate: LocalDate = LocalDate.parse("2016-06-06")

View File

@ -8,13 +8,14 @@ import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.NodeStartup 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_REGULATOR import net.corda.testing.DUMMY_REGULATOR
import net.corda.testing.IntegrationTest
import net.corda.testing.ProjectStructure.projectRootDir import net.corda.testing.ProjectStructure.projectRootDir
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Test import org.junit.Test
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.ScheduledExecutorService
class DriverTests { class DriverTests : IntegrationTest() {
companion object { companion object {
private val executorService: ScheduledExecutorService = Executors.newScheduledThreadPool(2) private val executorService: ScheduledExecutorService = Executors.newScheduledThreadPool(2)

View File

@ -66,7 +66,7 @@ fun testNodeConfiguration(
doReturn(emptyList<User>()).whenever(it).rpcUsers doReturn(emptyList<User>()).whenever(it).rpcUsers
doReturn(null).whenever(it).notary doReturn(null).whenever(it).notary
doReturn(makeTestDataSourceProperties(myLegalName.organisation)).whenever(it).dataSourceProperties doReturn(makeTestDataSourceProperties(myLegalName.organisation)).whenever(it).dataSourceProperties
doReturn(makeTestDatabaseProperties()).whenever(it).database doReturn(makeTestDatabaseProperties(myLegalName.organisation)).whenever(it).database
doReturn("").whenever(it).emailAddress doReturn("").whenever(it).emailAddress
doReturn("").whenever(it).exportJMXto doReturn("").whenever(it).exportJMXto
doReturn(true).whenever(it).devMode doReturn(true).whenever(it).devMode

View File

@ -42,6 +42,7 @@ import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.initialiseTestSerialization import net.corda.testing.initialiseTestSerialization
import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties
import net.corda.testing.testNodeConfiguration import net.corda.testing.testNodeConfiguration
import org.apache.activemq.artemis.utils.ReusableLatch import org.apache.activemq.artemis.utils.ReusableLatch
import org.slf4j.Logger import org.slf4j.Logger
@ -54,7 +55,6 @@ import java.security.PublicKey
import java.time.Clock import java.time.Clock
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import net.corda.testing.testNodeConfiguration
fun StartedNode<MockNetwork.MockNode>.pumpReceive(block: Boolean = false): InMemoryMessagingNetwork.MessageTransfer? { fun StartedNode<MockNetwork.MockNode>.pumpReceive(block: Boolean = false): InMemoryMessagingNetwork.MessageTransfer? {
return (network as InMemoryMessagingNetwork.InMemoryMessaging).pumpReceive(block) return (network as InMemoryMessagingNetwork.InMemoryMessaging).pumpReceive(block)
@ -360,7 +360,8 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
val config = testNodeConfiguration( val config = testNodeConfiguration(
baseDirectory = baseDirectory(id).createDirectories(), baseDirectory = baseDirectory(id).createDirectories(),
myLegalName = parameters.legalName ?: CordaX500Name(organisation = "Mock Company $id", locality = "London", country = "GB")).also { myLegalName = parameters.legalName ?: CordaX500Name(organisation = "Mock Company $id", locality = "London", country = "GB")).also {
doReturn(makeTestDataSourceProperties("node_${id}_net_$networkId")).whenever(it).dataSourceProperties doReturn(makeTestDataSourceProperties("node_$id", "net_$networkId")).whenever(it).dataSourceProperties
doReturn(makeTestDatabaseProperties("node_$id")).whenever(it).database
parameters.configOverrides(it) parameters.configOverrides(it)
} }
val node = nodeFactory(MockNodeArgs(config, this, id, parameters.entropyRoot)) val node = nodeFactory(MockNodeArgs(config, this, id, parameters.entropyRoot))

View File

@ -1,6 +1,9 @@
package net.corda.testing.node package net.corda.testing.node
import com.google.common.collect.MutableClassToInstanceMap import com.google.common.collect.MutableClassToInstanceMap
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import net.corda.core.cordapp.CordappProvider import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.* import net.corda.core.crypto.*
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
@ -20,6 +23,7 @@ import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.services.api.StateMachineRecordedTransactionMappingStorage import net.corda.node.services.api.StateMachineRecordedTransactionMappingStorage
import net.corda.node.services.api.VaultServiceInternal import net.corda.node.services.api.VaultServiceInternal
import net.corda.node.services.api.WritableTransactionStorage import net.corda.node.services.api.WritableTransactionStorage
import net.corda.node.services.config.configOf
import net.corda.node.services.identity.InMemoryIdentityService import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.node.services.keys.freshCertificate import net.corda.node.services.keys.freshCertificate
import net.corda.node.services.keys.getSigner import net.corda.node.services.keys.getSigner
@ -27,7 +31,6 @@ import net.corda.node.services.persistence.HibernateConfiguration
import net.corda.node.services.persistence.InMemoryStateMachineRecordedTransactionMappingStorage import net.corda.node.services.persistence.InMemoryStateMachineRecordedTransactionMappingStorage
import net.corda.node.services.schema.HibernateObserver import net.corda.node.services.schema.HibernateObserver
import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.services.statemachine.FlowStateMachineImpl
import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.services.transactions.InMemoryTransactionVerifierService
import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.NodeVaultService
import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.CordaPersistence
@ -58,42 +61,54 @@ open class MockServices(
@JvmStatic @JvmStatic
val MOCK_VERSION_INFO = VersionInfo(1, "Mock release", "Mock revision", "Mock Vendor") val MOCK_VERSION_INFO = VersionInfo(1, "Mock release", "Mock revision", "Mock Vendor")
private val systemProperties = System.getProperties().toList().map { it.first.toString() to it.second.toString() }.toMap() private fun readDatabaseConfig(nodeName: String? = null, postifx: String? = null): Config {
val standarizedNodeName = if (nodeName!= null) nodeName.replace(" ", "").replace("-", "_") else null
private val dbNames = mutableMapOf<String, String>() val h2InstanceName = if (postifx != null) standarizedNodeName + "_" + postifx else standarizedNodeName
val parseOptions = ConfigParseOptions.defaults()
val databaseConfig = ConfigFactory.parseResources(System.getProperty("databaseProvider") + ".conf", parseOptions.setAllowMissing(true))
val fixedOverride = ConfigFactory.parseString("baseDirectory = \"\"")
val nodeOrganizationNameConfig = if (standarizedNodeName != null) configOf("nodeOrganizationName" to standarizedNodeName) else ConfigFactory.empty()
val defaultProps = Properties()
defaultProps.setProperty("dataSourceProperties.dataSourceClassName", "org.h2.jdbcx.JdbcDataSource")
defaultProps.setProperty("dataSourceProperties.dataSource.url", "jdbc:h2:mem:${h2InstanceName}_persistence;LOCK_TIMEOUT=100;DB_CLOSE_ON_EXIT=TRUE")//;TRACE_LEVEL_SYSTEM_OUT=4")
defaultProps.setProperty("dataSourceProperties.dataSource.user", "sa")
defaultProps.setProperty("dataSourceProperties.dataSource.password", "")
val defaultConfig = ConfigFactory.parseProperties(defaultProps, parseOptions)
return databaseConfig.withFallback(fixedOverride)
.withFallback(nodeOrganizationNameConfig)
.withFallback(defaultConfig)
.resolve()
}
/** /**
* Make properties appropriate for creating a DataSource for unit tests. * Make properties appropriate for creating a DataSource for unit tests.
* *
* @param nodeName Reflects the "instance" of the in-memory database. Defaults to a random string. * @param nodeName Reflects the "instance" of the in-memory database or database username/schema. Defaults to a random string.
* @param nodeNameExtension Provides additional name extension for the "instance" of in-memory database to provide uniqnes when running unit tests against H2 db.
*/ */
// TODO: Can we use an X509 principal generator here? // TODO: Can we use an X509 principal generator here?
@JvmStatic @JvmStatic
fun makeTestDataSourceProperties(nodeName: String = SecureHash.randomSHA256().toString()): Properties { fun makeTestDataSourceProperties(nodeName: String = SecureHash.randomSHA256().toString(), nodeNameExtension: String? = null): Properties {
val overriddenDatasourceUrl = systemProperties["dataSourceProperties.dataSource.url"]?.let { property -> val config = readDatabaseConfig(nodeName, nodeNameExtension)
dbNames.computeIfAbsent(nodeName, { property + "/" + UUID.randomUUID().toString()})
}
val props = Properties() val props = Properties()
props.setProperty("dataSourceClassName", systemProperties["dataSourceProperties.dataSourceClassName"] ?: "org.h2.jdbcx.JdbcDataSource") props.setProperty("dataSourceClassName", config.getString("dataSourceProperties.dataSourceClassName"))
props.setProperty("dataSource.url", overriddenDatasourceUrl ?: "jdbc:h2:mem:${nodeName}_persistence;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE") props.setProperty("dataSource.url", config.getString("dataSourceProperties.dataSource.url"))
props.setProperty("dataSource.user", systemProperties["dataSourceProperties.dataSource.user"] ?: "sa") props.setProperty("dataSource.user", config.getString("dataSourceProperties.dataSource.user"))
props.setProperty("dataSource.password", systemProperties["dataSourceProperties.dataSource.password"] ?: "") props.setProperty("dataSource.password", config.getString("dataSourceProperties.dataSource.password"))
return props return props
} }
/** /**
* Make properties appropriate for creating a Database for unit tests. * Make properties appropriate for creating a Database for unit tests.
* *
* @param key (optional) key of a database property to be set. * @param nodeName Reflects the "instance" of the in-memory database or database username/schema.
* @param value (optional) value of a database property to be set.
*/ */
@JvmStatic @JvmStatic
fun makeTestDatabaseProperties(key: String? = null, value: String? = null): Properties { fun makeTestDatabaseProperties(nodeName: String? = null): Properties {
val props = Properties() val props = Properties()
props.setProperty("transactionIsolationLevel", "repeatableRead") //for other possible values see net.corda.node.utilities.CordaPeristence.parserTransactionIsolationLevel(String) props.setProperty("transactionIsolationLevel", "repeatableRead") //for other possible values see net.corda.node.utilities.CordaPeristence.parserTransactionIsolationLevel(String)
if (key != null) { if (nodeName != null) {
props.setProperty(key, value) props.setProperty("nodeOrganizationName", nodeName)
} }
return props return props
} }

View File

@ -26,6 +26,9 @@ dependencies {
// OkHTTP: Simple HTTP library. // OkHTTP: Simple HTTP library.
compile "com.squareup.okhttp3:okhttp:$okhttp_version" compile "com.squareup.okhttp3:okhttp:$okhttp_version"
// integration test helpers
compile "org.springframework:spring-jdbc:$spring_jdbc_version"
} }
jar { jar {

View File

@ -0,0 +1,57 @@
package net.corda.testing
import net.corda.testing.database.DbScriptRunner.runDbScript
import org.junit.After
import org.junit.AfterClass
import org.junit.Before
import org.junit.BeforeClass
/**
* Base class for all integration tests that require common setup and/or teardown.
* eg. serialization, database schema creation and data population / clean-up
*/
abstract class IntegrationTest {
// System properties set in main 'corda-project' build.gradle
// Note: the database provider configuration file for integration tests should specify:
// dataSource.user = ${nodeOrganizationName}
// dataSource.password = [PASSWORD]
// where [PASSWORD] must be the same for all ${nodeOrganizationName}
companion object {
private val DATABASE_PROVIDER = "databaseProvider"
private val dbProvider = System.getProperty(DATABASE_PROVIDER, "")
private val TEST_DB_SCRIPT_DIR = "test.db.script.dir"
private val testDbScriptDir = System.getProperty(TEST_DB_SCRIPT_DIR, "database-scripts")
@BeforeClass
@JvmStatic
fun globalSetUp() {
if (dbProvider.isNotEmpty()) {
runDbScript(dbProvider,"$testDbScriptDir/db-global-setup-${this::class.simpleName}.sql")
}
}
@AfterClass
@JvmStatic
fun globalTearDown() {
if (dbProvider.isNotEmpty()) {
runDbScript(dbProvider,"$testDbScriptDir/db-global-cleanup-${this::class.simpleName}.sql")
}
}
}
@Before
@Throws(Exception::class)
open fun setUp() {
if (dbProvider.isNotEmpty()) {
runDbScript(dbProvider,"$testDbScriptDir/db-setup-${this::class.simpleName}.sql")
}
}
@After
open fun tearDown() {
if (dbProvider.isNotEmpty()) {
runDbScript(dbProvider,"$testDbScriptDir/db-cleanup-${this::class.simpleName}.sql")
}
}
}

View File

@ -0,0 +1,33 @@
package net.corda.testing.database
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import net.corda.core.utilities.loggerFor
import org.springframework.core.io.ClassPathResource
import org.springframework.jdbc.datasource.DriverManagerDataSource
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator
object DbScriptRunner {
private val log = loggerFor<DbScriptRunner>()
// System properties set in main 'corda-project' build.gradle
private const val TEST_DB_ADMIN_USER = "test.db.admin.user"
private const val TEST_DB_ADMIN_PASSWORD = "test.db.admin.password"
fun runDbScript(dbProvider: String, initScript: String) {
val parseOptions = ConfigParseOptions.defaults()
val databaseConfig = ConfigFactory.parseResources("$dbProvider.conf", parseOptions.setAllowMissing(false))
val dataSource = DriverManagerDataSource()
dataSource.setDriverClassName(databaseConfig.getString("dataSourceProperties.dataSourceClassName"))
dataSource.url = databaseConfig.getString("dataSourceProperties.dataSource.url")
dataSource.username = System.getProperty(TEST_DB_ADMIN_USER)
dataSource.password = System.getProperty(TEST_DB_ADMIN_PASSWORD)
val initSchema = ClassPathResource(initScript )
if (initSchema.exists()) {
val databasePopulator = ResourceDatabasePopulator(false, true, null, initSchema)
DatabasePopulatorUtils.execute(databasePopulator, dataSource)
}
else log.warn("DB Script missing: $initSchema")
}
}