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.commons_collections_version = '4.1'
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:
ext.java8_minUpdateVersion = '131'
@ -150,13 +151,20 @@ allprojects {
final AMQP_ENABLE_PROP_NAME = "net.corda.testing.amqp.enable"
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_CLASSNAME = "dataSourceProperties.dataSourceClassName"
final DATASOURCE_USER = "dataSourceProperties.dataSource.user"
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)
if (property != null) {
systemProperty(it, property)

View File

@ -19,12 +19,12 @@ import net.corda.testing.driver.driver
import org.junit.Test
import kotlin.test.assertEquals
class IntegrationTestingTutorial {
class IntegrationTestingTutorial : IntegrationTest() {
@Test
fun `alice bob cash exchange example`() {
// START 1
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(
startFlow<CashIssueFlow>(),
startFlow<CashPaymentFlow>(),

View File

@ -43,16 +43,24 @@ TODO: Add instructions on manual 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
or other database setup (H2 in server mode for example), while running tests extra parameters can be used to specify required
properties. Appropriate changes will then be applied to all tests in a appropriate manner.
By default, all tests which need a database, utilize built-in H2 instances. For the purpose of testing other relational
database providers or different database setups (for example, H2 in server mode), we introduce an optional system
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
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.
- ``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.finance.EUR
import net.corda.finance.USD
import net.corda.testing.IntegrationTest
import net.corda.testing.driver.driver
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
class CashConfigDataFlowTest {
class CashConfigDataFlowTest : IntegrationTest() {
@Test
fun `issuable currencies are read in from node config`() {
driver {

View File

@ -10,6 +10,7 @@ import net.corda.node.internal.NodeStartup
import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.nodeapi.User
import net.corda.testing.ALICE
import net.corda.testing.IntegrationTest
import net.corda.testing.ProjectStructure.projectRootDir
import net.corda.testing.driver.driver
import org.assertj.core.api.Assertions.assertThat
@ -19,7 +20,7 @@ import java.io.*
import java.nio.file.Files
import kotlin.test.assertEquals
class BootTests {
class BootTests : IntegrationTest() {
@Test
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.testing.ALICE
import net.corda.testing.BOB
import net.corda.testing.IntegrationTest
import net.corda.testing.chooseIdentity
import net.corda.testing.driver.driver
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
class CordappScanningDriverTest {
class CordappScanningDriverTest : IntegrationTest() {
@Test
fun `sub-classed initiated flow pointing to the same initiating flow as its super-class`() {
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.nodeapi.User
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.IntegrationTest
import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.driver
import net.corda.testing.node.NotarySpec
@ -38,7 +39,7 @@ private fun checkQuasarAgent() {
}
@Ignore("Run these locally")
class NodePerformanceTests {
class NodePerformanceTests : IntegrationTest() {
@StartableByRPC
class EmptyFlow : FlowLogic<Unit>() {
@Suspendable
@ -54,6 +55,7 @@ class NodePerformanceTests {
@Before
fun before() {
checkQuasarAgent()
super.setUp()
}
@Test

View File

@ -1,6 +1,7 @@
package net.corda.node
import com.google.common.base.Stopwatch
import net.corda.testing.IntegrationTest
import net.corda.testing.driver.driver
import org.junit.Ignore
import org.junit.Test
@ -8,7 +9,7 @@ import java.util.*
import java.util.concurrent.TimeUnit
@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.
@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_NOTARY
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.IntegrationTest
import net.corda.testing.driver.DriverDSLExposedInterface
import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.driver
@ -32,7 +33,7 @@ import java.net.URLClassLoader
import java.nio.file.Files
import kotlin.test.assertFailsWith
class AttachmentLoadingTests {
class AttachmentLoadingTests : IntegrationTest() {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
@ -79,6 +80,7 @@ class AttachmentLoadingTests {
@Before
fun setup() {
super.setUp()
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.MockNodeParameters
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.nio.file.Paths
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class BFTNotaryServiceTests {
private val mockNet = MockNetwork()
private lateinit var mockNet: MockNetwork
private lateinit var notary: Party
private lateinit var node: StartedNode<MockNode>
@Before
fun before() {
mockNet = MockNetwork()
node = mockNet.createNode()
}
@After
fun stopNodes() {
mockNet.stopNodes()

View File

@ -24,7 +24,7 @@ import org.junit.Test
import rx.Observable
import java.util.*
class DistributedServiceTests {
class DistributedServiceTests : IntegrationTest() {
private lateinit var alice: NodeHandle
private lateinit var notaryNodes: List<NodeHandle.OutOfProcess>
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.services.transactions.RaftValidatingNotaryService
import net.corda.testing.DUMMY_BANK_A
import net.corda.testing.IntegrationTest
import net.corda.testing.chooseIdentity
import net.corda.testing.contracts.DummyContract
import net.corda.testing.driver.NodeHandle
@ -25,7 +26,7 @@ import java.util.*
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class RaftNotaryServiceTests {
class RaftNotaryServiceTests : IntegrationTest() {
private val notaryName = CordaX500Name(RaftValidatingNotaryService.id, "RAFT Notary Service", "London", "GB")
@Test

View File

@ -17,6 +17,7 @@ import net.corda.node.internal.StartedNode
import net.corda.node.services.messaging.*
import net.corda.node.services.transactions.RaftValidatingNotaryService
import net.corda.testing.ALICE
import net.corda.testing.IntegrationTest
import net.corda.testing.chooseIdentity
import net.corda.testing.driver.DriverDSLExposedInterface
import net.corda.testing.driver.NodeHandle
@ -30,7 +31,7 @@ import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
class P2PMessagingTest {
class P2PMessagingTest : IntegrationTest() {
private companion object {
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.startFlow
import net.corda.nodeapi.User
import net.corda.testing.IntegrationTest
import net.corda.testing.chooseIdentity
import net.corda.testing.driver.driver
import org.junit.Assume.assumeFalse
@ -31,7 +32,7 @@ import javax.persistence.Table
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
class NodeStatePersistenceTests {
class NodeStatePersistenceTests : IntegrationTest() {
@Test
fun `persistent state survives node restart`() {
// 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.node.utilities.*
import net.corda.nodeapi.config.SSLConfiguration
import net.corda.nodeapi.config.toProperties
import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralSubtree
import org.bouncycastle.asn1.x509.NameConstraints
@ -27,10 +28,25 @@ object ConfigHelper {
val parseOptions = ConfigParseOptions.defaults()
val defaultConfig = ConfigFactory.parseResources("reference.conf", parseOptions.setAllowMissing(false))
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
// Add substitution values here
.withFallback(systemUnquotedPlaceholders)
.withFallback(configOf("nodeOrganizationName" to baseDirectory.fileName.toString().replace(" ","").replace("-","_")))
.withFallback(ConfigFactory.systemProperties())
.withFallback( configOf("baseDirectory" to baseDirectory.toString()))
.withFallback(databaseConfig)
.withFallback(appConfig)
.withFallback(defaultConfig)
.resolve()

View File

@ -89,7 +89,7 @@ class DistributedImmutableMapTests {
private fun createReplica(myAddress: NetworkHostAndPort, clusterAddress: NetworkHostAndPort? = null): CompletableFuture<Member> {
val storage = Storage.builder().withStorageLevel(StorageLevel.MEMORY).build()
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)
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.testing.DUMMY_BANK_A
import net.corda.testing.DUMMY_BANK_B
import net.corda.testing.IntegrationTest
import net.corda.testing.driver.PortAllocation
import net.corda.testing.driver.driver
import org.junit.Test
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).
// Force INFO logging to prevent printing 10MB arrays in logfiles
@Test

View File

@ -4,11 +4,12 @@ import net.corda.bank.api.BankOfCordaClientApi
import net.corda.bank.api.BankOfCordaWebApi.IssueRequestParams
import net.corda.core.utilities.getOrThrow
import net.corda.testing.BOC
import net.corda.testing.IntegrationTest
import net.corda.testing.driver.driver
import org.junit.Test
import kotlin.test.assertTrue
class BankOfCordaHttpAPITest {
class BankOfCordaHttpAPITest : IntegrationTest() {
@Test
fun `issuer flow via Http`() {
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 org.junit.Test
class BankOfCordaRPCClientTest {
class BankOfCordaRPCClientTest : IntegrationTest() {
@Test
fun `issuer flow via RPC`() {
val commonPermissions = setOf(

View File

@ -33,7 +33,7 @@ import rx.Observable
import java.time.Duration
import java.time.LocalDate
class IRSDemoTest {
class IRSDemoTest : IntegrationTest() {
companion object {
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.testing.DUMMY_BANK_A
import net.corda.testing.DUMMY_BANK_B
import net.corda.testing.IntegrationTest
import net.corda.testing.driver.driver
import net.corda.testing.http.HttpApi
import net.corda.vega.api.PortfolioApi
@ -16,7 +17,7 @@ import org.junit.Test
import java.math.BigDecimal
import java.time.LocalDate
class SimmValuationTest {
class SimmValuationTest : IntegrationTest() {
private companion object {
// 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")

View File

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

View File

@ -66,7 +66,7 @@ fun testNodeConfiguration(
doReturn(emptyList<User>()).whenever(it).rpcUsers
doReturn(null).whenever(it).notary
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).exportJMXto
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.node.MockServices.Companion.MOCK_VERSION_INFO
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties
import net.corda.testing.testNodeConfiguration
import org.apache.activemq.artemis.utils.ReusableLatch
import org.slf4j.Logger
@ -54,7 +55,6 @@ import java.security.PublicKey
import java.time.Clock
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import net.corda.testing.testNodeConfiguration
fun StartedNode<MockNetwork.MockNode>.pumpReceive(block: Boolean = false): InMemoryMessagingNetwork.MessageTransfer? {
return (network as InMemoryMessagingNetwork.InMemoryMessaging).pumpReceive(block)
@ -360,7 +360,8 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
val config = testNodeConfiguration(
baseDirectory = baseDirectory(id).createDirectories(),
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)
}
val node = nodeFactory(MockNodeArgs(config, this, id, parameters.entropyRoot))

View File

@ -1,6 +1,9 @@
package net.corda.testing.node
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.crypto.*
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.VaultServiceInternal
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.keys.freshCertificate
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.schema.HibernateObserver
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.vault.NodeVaultService
import net.corda.node.utilities.CordaPersistence
@ -58,42 +61,54 @@ open class MockServices(
@JvmStatic
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 val dbNames = mutableMapOf<String, String>()
private fun readDatabaseConfig(nodeName: String? = null, postifx: String? = null): Config {
val standarizedNodeName = if (nodeName!= null) nodeName.replace(" ", "").replace("-", "_") else null
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.
*
* @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?
@JvmStatic
fun makeTestDataSourceProperties(nodeName: String = SecureHash.randomSHA256().toString()): Properties {
val overriddenDatasourceUrl = systemProperties["dataSourceProperties.dataSource.url"]?.let { property ->
dbNames.computeIfAbsent(nodeName, { property + "/" + UUID.randomUUID().toString()})
}
fun makeTestDataSourceProperties(nodeName: String = SecureHash.randomSHA256().toString(), nodeNameExtension: String? = null): Properties {
val config = readDatabaseConfig(nodeName, nodeNameExtension)
val props = Properties()
props.setProperty("dataSourceClassName", systemProperties["dataSourceProperties.dataSourceClassName"] ?: "org.h2.jdbcx.JdbcDataSource")
props.setProperty("dataSource.url", overriddenDatasourceUrl ?: "jdbc:h2:mem:${nodeName}_persistence;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE")
props.setProperty("dataSource.user", systemProperties["dataSourceProperties.dataSource.user"] ?: "sa")
props.setProperty("dataSource.password", systemProperties["dataSourceProperties.dataSource.password"] ?: "")
props.setProperty("dataSourceClassName", config.getString("dataSourceProperties.dataSourceClassName"))
props.setProperty("dataSource.url", config.getString("dataSourceProperties.dataSource.url"))
props.setProperty("dataSource.user", config.getString("dataSourceProperties.dataSource.user"))
props.setProperty("dataSource.password", config.getString("dataSourceProperties.dataSource.password"))
return props
}
/**
* Make properties appropriate for creating a Database for unit tests.
*
* @param key (optional) key of a database property to be set.
* @param value (optional) value of a database property to be set.
* @param nodeName Reflects the "instance" of the in-memory database or database username/schema.
*/
@JvmStatic
fun makeTestDatabaseProperties(key: String? = null, value: String? = null): Properties {
fun makeTestDatabaseProperties(nodeName: String? = null): Properties {
val props = Properties()
props.setProperty("transactionIsolationLevel", "repeatableRead") //for other possible values see net.corda.node.utilities.CordaPeristence.parserTransactionIsolationLevel(String)
if (key != null) {
props.setProperty(key, value)
if (nodeName != null) {
props.setProperty("nodeOrganizationName", nodeName)
}
return props
}

View File

@ -26,6 +26,9 @@ dependencies {
// OkHTTP: Simple HTTP library.
compile "com.squareup.okhttp3:okhttp:$okhttp_version"
// integration test helpers
compile "org.springframework:spring-jdbc:$spring_jdbc_version"
}
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")
}
}