From 359610ff140b6f38d3c00dadbf64986c51e6adcc Mon Sep 17 00:00:00 2001 From: szymonsztuka Date: Mon, 13 Nov 2017 17:29:57 +0000 Subject: [PATCH] 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. --- build.gradle | 12 +++- .../corda/docs/IntegrationTestingTutorial.kt | 4 +- docs/source/testing.rst | 20 +++++-- .../finance/flows/CashConfigDataFlowTest.kt | 3 +- .../kotlin/net/corda/node/BootTests.kt | 3 +- .../corda/node/CordappScanningDriverTest.kt | 3 +- .../net/corda/node/NodePerformanceTests.kt | 4 +- .../corda/node/NodeStartupPerformanceTests.kt | 3 +- .../node/services/AttachmentLoadingTests.kt | 4 +- .../node/services/BFTNotaryServiceTests.kt | 8 ++- .../node/services/DistributedServiceTests.kt | 2 +- .../node/services/RaftNotaryServiceTests.kt | 3 +- .../services/messaging/P2PMessagingTest.kt | 3 +- .../test/node/NodeStatePersistenceTests.kt | 3 +- .../node/services/config/ConfigUtilities.kt | 16 ++++++ .../DistributedImmutableMapTests.kt | 2 +- .../attachmentdemo/AttachmentDemoTest.kt | 3 +- .../net/corda/bank/BankOfCordaHttpAPITest.kt | 3 +- .../corda/bank/BankOfCordaRPCClientTest.kt | 2 +- .../kotlin/net/corda/irs/IRSDemoTest.kt | 2 +- .../net/corda/vega/SimmValuationTest.kt | 3 +- .../net/corda/testing/driver/DriverTests.kt | 3 +- .../kotlin/net/corda/testing/NodeTestUtils.kt | 2 +- .../kotlin/net/corda/testing/node/MockNode.kt | 5 +- .../net/corda/testing/node/MockServices.kt | 53 ++++++++++------- testing/test-utils/build.gradle | 3 + .../net/corda/testing/IntegrationTest.kt | 57 +++++++++++++++++++ .../corda/testing/database/DbScriptRunner.kt | 33 +++++++++++ 28 files changed, 212 insertions(+), 50 deletions(-) create mode 100644 testing/test-utils/src/main/kotlin/net/corda/testing/IntegrationTest.kt create mode 100644 testing/test-utils/src/main/kotlin/net/corda/testing/database/DbScriptRunner.kt diff --git a/build.gradle b/build.gradle index 3bc11e414c..f7ba357b02 100644 --- a/build.gradle +++ b/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) diff --git a/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt b/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt index 9b192038bf..95ffd9ef45 100644 --- a/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt +++ b/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt @@ -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(), startFlow(), diff --git a/docs/source/testing.rst b/docs/source/testing.rst index 5e9cae1c24..c4b5c62bfa 100644 --- a/docs/source/testing.rst +++ b/docs/source/testing.rst @@ -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) \ No newline at end of file +- ``dataSourceProperties.dataSource.password`` - JDBC password + +- ``database.transactionIsolationLevel`` - Isolation level pertinent to relevant database provider + +All defaults are taken from the ``reference.conf`` file. \ No newline at end of file diff --git a/finance/src/integration-test/kotlin/net/corda/finance/flows/CashConfigDataFlowTest.kt b/finance/src/integration-test/kotlin/net/corda/finance/flows/CashConfigDataFlowTest.kt index 49351f1e13..f67cc17a0e 100644 --- a/finance/src/integration-test/kotlin/net/corda/finance/flows/CashConfigDataFlowTest.kt +++ b/finance/src/integration-test/kotlin/net/corda/finance/flows/CashConfigDataFlowTest.kt @@ -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 { diff --git a/node/src/integration-test/kotlin/net/corda/node/BootTests.kt b/node/src/integration-test/kotlin/net/corda/node/BootTests.kt index 6c2c3d3ae7..1646cc1faa 100644 --- a/node/src/integration-test/kotlin/net/corda/node/BootTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/BootTests.kt @@ -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`() { diff --git a/node/src/integration-test/kotlin/net/corda/node/CordappScanningDriverTest.kt b/node/src/integration-test/kotlin/net/corda/node/CordappScanningDriverTest.kt index 8515b9b4ec..ff24a80bd6 100644 --- a/node/src/integration-test/kotlin/net/corda/node/CordappScanningDriverTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/CordappScanningDriverTest.kt @@ -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())) diff --git a/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt b/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt index abf4b1a06b..751566fe7e 100644 --- a/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt @@ -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() { @Suspendable @@ -54,6 +55,7 @@ class NodePerformanceTests { @Before fun before() { checkQuasarAgent() + super.setUp() } @Test diff --git a/node/src/integration-test/kotlin/net/corda/node/NodeStartupPerformanceTests.kt b/node/src/integration-test/kotlin/net/corda/node/NodeStartupPerformanceTests.kt index a9f2586013..9b16610edf 100644 --- a/node/src/integration-test/kotlin/net/corda/node/NodeStartupPerformanceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/NodeStartupPerformanceTests.kt @@ -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 diff --git a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt index 0b233b9fb3..aa6d478d2d 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt @@ -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() } diff --git a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt index dafdb08c7f..94b83d95d4 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt @@ -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 + @Before + fun before() { + mockNet = MockNetwork() + node = mockNet.createNode() + } @After fun stopNodes() { mockNet.stopNodes() diff --git a/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt index 15a8914e35..e3e6d6e2c6 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt @@ -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 private lateinit var aliceProxy: CordaRPCOps diff --git a/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt index b9e95db5bd..00a0a97d93 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt @@ -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 diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt index 6d480bdf87..c0ac949582 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt @@ -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") } diff --git a/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt b/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt index c2355f7723..5eceff5ee2 100644 --- a/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt @@ -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. diff --git a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt index b3cec7de64..e5b71712a8 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt @@ -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() diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/DistributedImmutableMapTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/DistributedImmutableMapTests.kt index f2ca28c8ec..9834f1f7a3 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/DistributedImmutableMapTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/DistributedImmutableMapTests.kt @@ -89,7 +89,7 @@ class DistributedImmutableMapTests { private fun createReplica(myAddress: NetworkHostAndPort, clusterAddress: NetworkHostAndPort? = null): CompletableFuture { 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) } diff --git a/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt b/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt index 42f04aa714..d4b0740ef6 100644 --- a/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt +++ b/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt @@ -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 diff --git a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaHttpAPITest.kt b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaHttpAPITest.kt index 0f37be7cbc..ed510e58de 100644 --- a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaHttpAPITest.kt +++ b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaHttpAPITest.kt @@ -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) { diff --git a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt index 4b2e6c2580..67b9e7d53c 100644 --- a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt +++ b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt @@ -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( diff --git a/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt b/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt index ca9b841d9f..0efd48d8d8 100644 --- a/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt +++ b/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt @@ -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() } diff --git a/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt b/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt index d09d8f5a8c..2747e4961b 100644 --- a/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt +++ b/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt @@ -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") diff --git a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt index 12507b3b5e..668583c405 100644 --- a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt +++ b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt @@ -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) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt index 24ee8e0341..27c4bd5fe7 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt @@ -66,7 +66,7 @@ fun testNodeConfiguration( doReturn(emptyList()).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 diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index 9988dc16b3..cd33e5875c 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -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.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)) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt index 1261e93fe5..c427f97060 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -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() + 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 } diff --git a/testing/test-utils/build.gradle b/testing/test-utils/build.gradle index c90b00304e..74a2ff763b 100644 --- a/testing/test-utils/build.gradle +++ b/testing/test-utils/build.gradle @@ -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 { diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/IntegrationTest.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/IntegrationTest.kt new file mode 100644 index 0000000000..c01c74c0e6 --- /dev/null +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/IntegrationTest.kt @@ -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") + } + } + +} \ No newline at end of file diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/database/DbScriptRunner.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/database/DbScriptRunner.kt new file mode 100644 index 0000000000..10e3b1a1bf --- /dev/null +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/database/DbScriptRunner.kt @@ -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() + + // 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") + } +}