diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index c96e19edd..f0dd31427 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -250,11 +250,11 @@ jobs: - H2 include: - module: taskana-core - database: POSTGRES_10 + database: POSTGRES - module: taskana-core - database: DB2_11_1 + database: DB2 - module: taskana-rest-spring-example-boot - database: DB2_11_1 + database: DB2 steps: - name: Git checkout uses: actions/checkout@v2.3.4 @@ -281,6 +281,8 @@ jobs: run: ./mvnw -B validate -pl :taskana-rest-spring - name: Test run: ./mvnw -B verify -pl :${{matrix.module}} -Dcheckstyle.skip + env: + db.type: ${{ matrix.database }} - name: Upload JaCoCo Report if: matrix.database == 'H2' uses: actions/upload-artifact@v2 diff --git a/common/taskana-common-test/pom.xml b/common/taskana-common-test/pom.xml index 5fa3f6fba..09fa2e8de 100644 --- a/common/taskana-common-test/pom.xml +++ b/common/taskana-common-test/pom.xml @@ -106,6 +106,28 @@ org.springframework spring-webmvc + + org.testcontainers + db2 + ${version.testcontainers} + + + junit + junit + + + + + org.testcontainers + postgresql + ${version.testcontainers} + + + junit + junit + + + diff --git a/common/taskana-common-test/src/main/java/org/junit/rules/TestRule.java b/common/taskana-common-test/src/main/java/org/junit/rules/TestRule.java new file mode 100644 index 000000000..74f5e4ae5 --- /dev/null +++ b/common/taskana-common-test/src/main/java/org/junit/rules/TestRule.java @@ -0,0 +1,8 @@ +package org.junit.rules; + +// Testcontainers currently requires junit4 as a runtime dependency. +// Because we use junit5 we have to use this workaround to "simulate" the classes testcontainers +// requires in the classpath. They are not used unless a junit4 runtime is used. +// See: https://github.com/testcontainers/testcontainers-java/issues/970#issuecomment-625044008 +@SuppressWarnings("unused") +public interface TestRule {} diff --git a/common/taskana-common-test/src/main/java/org/junit/runners/model/Statement.java b/common/taskana-common-test/src/main/java/org/junit/runners/model/Statement.java new file mode 100644 index 000000000..add87960e --- /dev/null +++ b/common/taskana-common-test/src/main/java/org/junit/runners/model/Statement.java @@ -0,0 +1,8 @@ +package org.junit.runners.model; + +// Testcontainers currently requires junit4 as a runtime dependency. +// Because we use junit5 we have to use this workaround to "simulate" the classes testcontainers +// requires in the classpath. They are not used unless a junit4 runtime is used. +// See: https://github.com/testcontainers/testcontainers-java/issues/970#issuecomment-625044008 +@SuppressWarnings("unused") +public interface Statement {} diff --git a/common/taskana-common-test/src/main/java/pro/taskana/common/test/config/TestContainerExtension.java b/common/taskana-common-test/src/main/java/pro/taskana/common/test/config/TestContainerExtension.java new file mode 100644 index 000000000..f521a9b8e --- /dev/null +++ b/common/taskana-common-test/src/main/java/pro/taskana/common/test/config/TestContainerExtension.java @@ -0,0 +1,136 @@ +package pro.taskana.common.test.config; + +import static java.time.temporal.ChronoUnit.SECONDS; + +import java.time.Duration; +import java.util.Optional; +import java.util.UUID; +import javax.sql.DataSource; +import org.apache.ibatis.datasource.pooled.PooledDataSource; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.testcontainers.containers.Db2Container; +import org.testcontainers.containers.JdbcDatabaseContainer; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; +import org.testcontainers.utility.DockerImageName; + +import pro.taskana.common.internal.configuration.DB; + +public class TestContainerExtension implements AfterAllCallback { + + private final DataSource dataSource; + private final String schemaName; + private final JdbcDatabaseContainer container; + + public TestContainerExtension() { + DB db = getTestDatabase(); + Optional> container = createDockerContainer(db); + if (container.isPresent()) { + this.container = container.get(); + this.container.start(); + dataSource = createDataSource(this.container); + } else { + dataSource = createDataSourceForH2(); + this.container = null; + } + schemaName = determineSchemaName(db); + } + + @Override + public void afterAll(ExtensionContext context) { + if (container != null) { + container.stop(); + } + } + + public DataSource getDataSource() { + return dataSource; + } + + public String getSchemaName() { + return schemaName; + } + + private DB getTestDatabase() { + String property = System.getenv("db.type"); + DB db; + try { + db = DB.valueOf(property); + } catch (Exception ex) { + db = DB.H2; + } + return db; + } + + private static String determineSchemaName(DB db) { + return db == DB.POSTGRES ? "taskana" : "TASKANA"; + } + + private static DataSource createDataSource(JdbcDatabaseContainer container) { + PooledDataSource ds = + new PooledDataSource( + Thread.currentThread().getContextClassLoader(), + container.getDriverClassName(), + container.getJdbcUrl(), + container.getUsername(), + container.getPassword()); + ds.setPoolTimeToWait(50); + ds.forceCloseAll(); // otherwise, the MyBatis pool is not initialized correctly + return ds; + } + + private static Optional> createDockerContainer(DB db) { + switch (db) { + case DB2: + return Optional.of( + new Db2Container( + DockerImageName.parse("taskana/db2:11.1") + .asCompatibleSubstituteFor("ibmcom/db2")) + .waitingFor( + new LogMessageWaitStrategy() + .withRegEx(".*DB2START processing was successful.*") + .withStartupTimeout(Duration.of(60, SECONDS))) + .withUsername("db2inst1") + .withPassword("db2inst1-pwd") + .withDatabaseName("TSKDB")); + case POSTGRES: + return Optional.of( + new PostgreSQLContainer<>(DockerImageName.parse("postgres:10")) + .withUsername("postgres") + .withPassword("postgres") + .withDatabaseName("postgres") + .withCommand( + "/bin/sh", + "-c", + "localedef -i de_DE -c -f UTF-8 -A /usr/share/locale/locale.alias de_DE.UTF-8 " + + "&& export LANG=de_DE.UTF-8 " + + "&& ./docker-entrypoint.sh postgres -c fsync=off") + .waitingFor( + new LogMessageWaitStrategy() + .withRegEx(".*Datenbanksystem ist bereit, um Verbindungen anzunehmen.*\\s") + .withTimes(2) + .withStartupTimeout(Duration.of(60, SECONDS)))); + default: + return Optional.empty(); + } + } + + private static DataSource createDataSourceForH2() { + PooledDataSource ds = + new PooledDataSource( + Thread.currentThread().getContextClassLoader(), + "org.h2.Driver", + "jdbc:h2:mem:" + + UUID.randomUUID() + + ";LOCK_MODE=0;" + + "INIT=CREATE SCHEMA IF NOT EXISTS TASKANA\\;" + + "SET COLLATION DEFAULT_de_DE ", + "sa", + "sa"); + ds.setPoolTimeToWait(50); + ds.forceCloseAll(); // otherwise, the MyBatis pool is not initialized correctly + + return ds; + } +} diff --git a/docker-databases/prepare_db.sh b/docker-databases/prepare_db.sh index a6d6068a3..a5865f08b 100755 --- a/docker-databases/prepare_db.sh +++ b/docker-databases/prepare_db.sh @@ -19,8 +19,8 @@ export TOP_PID=$$ #H #H database: #H - H2 -#H - DB2_11_1 -#H - POSTGRES_10 +#H - DB2 | DB2_11_1 +#H - POSTGRES | POSTGRES_10 # Arguments: # $1: exit code function helpAndExit() { @@ -34,10 +34,10 @@ function helpAndExit() { function mapDBToDockerComposeServiceName() { [[ -z "$1" || "$1" == "H2" ]] && return case "$1" in - DB2_11_1) + DB2|DB2_11_1) echo "taskana-db2_11-1" ;; - POSTGRES_10) + POSTGRES|POSTGRES_10) echo "taskana-postgres_10" ;; *) @@ -54,7 +54,7 @@ function main() { H2) [[ -f "$propFile" ]] && rm "$propFile" ;; - DB2_11_1) + DB2|DB2_11_1) docker-compose -f $scriptDir/docker-compose.yml up -d "$(mapDBToDockerComposeServiceName "$1")" echo 'jdbcDriver=com.ibm.db2.jcc.DB2Driver' > $propFile @@ -63,7 +63,7 @@ function main() { echo 'dbPassword=db2inst1-pwd' >> $propFile echo 'schemaName=TASKANA' >> $propFile ;; - POSTGRES_10) + POSTGRES|POSTGRES_10) docker-compose -f $scriptDir/docker-compose.yml up -d "$(mapDBToDockerComposeServiceName "$1")" echo 'jdbcDriver=org.postgresql.Driver' > $propFile diff --git a/lib/taskana-core/src/test/java/acceptance/TaskanaIntegrationTestExtension.java b/lib/taskana-core/src/test/java/acceptance/TaskanaIntegrationTestExtension.java index 5e36dc672..b72a9bd79 100644 --- a/lib/taskana-core/src/test/java/acceptance/TaskanaIntegrationTestExtension.java +++ b/lib/taskana-core/src/test/java/acceptance/TaskanaIntegrationTestExtension.java @@ -1,19 +1,22 @@ package acceptance; +import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; -import pro.taskana.common.test.config.DataSourceGenerator; +import pro.taskana.common.test.config.TestContainerExtension; -public class TaskanaIntegrationTestExtension implements ParameterResolver { +public class TaskanaIntegrationTestExtension implements ParameterResolver, AfterAllCallback { private final TaskanaDependencyInjectionExtension dependencyInjectionExtension; + private final TestContainerExtension testContainerExtension; public TaskanaIntegrationTestExtension() throws Exception { + testContainerExtension = new TestContainerExtension(); dependencyInjectionExtension = - new TaskanaDependencyInjectionExtension(DataSourceGenerator.getDataSource()); + new TaskanaDependencyInjectionExtension(testContainerExtension.getDataSource()); } @Override @@ -29,4 +32,9 @@ public class TaskanaIntegrationTestExtension implements ParameterResolver { throws ParameterResolutionException { return dependencyInjectionExtension.resolveParameter(parameterContext, extensionContext); } + + @Override + public void afterAll(ExtensionContext context) { + testContainerExtension.afterAll(context); + } } diff --git a/lib/taskana-core/src/test/resources/container-license-acceptance.txt b/lib/taskana-core/src/test/resources/container-license-acceptance.txt new file mode 100644 index 000000000..7f43861ab --- /dev/null +++ b/lib/taskana-core/src/test/resources/container-license-acceptance.txt @@ -0,0 +1 @@ +taskana/db2:11.1 \ No newline at end of file diff --git a/pom.xml b/pom.xml index 936f32c57..0327e9038 100644 --- a/pom.xml +++ b/pom.xml @@ -88,6 +88,7 @@ 0.8.7 1.2.0 2.0.11 + 1.16.0 1.14.0