mirror of
https://github.com/corda/corda.git
synced 2024-12-27 08:22:35 +00:00
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:
parent
523a6db0b9
commit
359610ff14
12
build.gradle
12
build.gradle
@ -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)
|
||||
|
@ -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>(),
|
||||
|
@ -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.
|
@ -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 {
|
||||
|
@ -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`() {
|
||||
|
@ -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>()))
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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()
|
||||
|
@ -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) }
|
||||
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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(
|
||||
|
@ -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>()
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user