diff --git a/README.md b/README.md index 07ac7b7..4e78742 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ The primary goal of this project is to make it easier to write Spring-powered in ## Supported Integrations * Supports both `Spring` and `Spring Boot` frameworks - * Spring `4.3.8` - `6.1.x` - * Spring Boot `1.4.6` - `3.2.x` + * Spring `4.3.8` - `6.2.x` + * Spring Boot `1.4.6` - `3.4.x` * Supports multiple different databases * [PostgreSQL](#postgresql), [MSSQL](#microsoft-sql-server), [MySQL](#mysql), [MariaDB](#mariadb), [H2](#h2), [HSQLDB](#hsqldb), [Derby](#derby) * Supports multiple database providers diff --git a/build.gradle b/build.gradle index 77520d7..49d65d0 100644 --- a/build.gradle +++ b/build.gradle @@ -53,11 +53,12 @@ ext { [name: 'no_sb', liquibase: 'default'] ]], [name: 'postgres', versions: [ - [name: '12', postgres: '12.18', 'zonky-postgres': '12.18.0', opentable: 'default', yandex: 'default'], - [name: '13', postgres: '13.14', 'zonky-postgres': '13.14.0', opentable: 'default', yandex: 'default'], - [name: '14', postgres: '14.11', 'zonky-postgres': '14.11.0', opentable: 'default', yandex: 'default'], - [name: '15', postgres: '15.6', 'zonky-postgres': '15.6.0', opentable: 'default', yandex: 'default'], - [name: '16', postgres: '16.2', 'zonky-postgres': '16.2.0', opentable: 'default', yandex: 'default'] + [name: '12', postgres: '12.22', 'zonky-postgres': '12.22.0', opentable: 'default', yandex: 'default'], + [name: '13', postgres: '13.18', 'zonky-postgres': '13.18.0', opentable: 'default', yandex: 'default'], + [name: '14', postgres: '14.15', 'zonky-postgres': '14.15.0', opentable: 'default', yandex: 'default'], + [name: '15', postgres: '15.10', 'zonky-postgres': '15.10.0', opentable: 'default', yandex: 'default'], + [name: '16', postgres: '16.6', 'zonky-postgres': '16.6.0', opentable: 'default', yandex: 'default'], + [name: '17', postgres: '17.2', 'zonky-postgres': '17.2.0', opentable: 'default', yandex: 'default'] ]], [name: 'mssql', versions: [ [name: '2017', 'mssql': '2017-latest', 'mssql-driver': 'default'], @@ -102,16 +103,19 @@ if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_17)) { testSuites.find { it.name == 'flyway' }.versions += [ [name: '9.9.0', flyway: '9.9.0', 'flyway-test': '9.5.0', spring: '6.0.14', 'spring-boot': '3.0.13', 'zonky-postgres': 'default'], - [name: '9.16.3', flyway: '9.16.3', 'flyway-test': '9.5.0', spring: '6.0.18', 'spring-boot': '3.1.10', 'zonky-postgres': 'default'], - [name: '9.22.3', flyway: '9.22.3', 'flyway-test': '9.5.0', spring: '6.1.5', 'spring-boot': '3.2.4', 'zonky-postgres': 'default'], - [name: '10.0.1', flyway: '10.0.1', 'flyway-test': '10.0.0', spring: '6.1.5', 'spring-boot': '3.2.4', 'zonky-postgres': 'default'], - [name: '10.11.0', flyway: '10.5.0', 'flyway-test': '10.0.0', spring: '6.1.5', 'spring-boot': '3.2.4', 'zonky-postgres': 'default'] + [name: '9.16.3', flyway: '9.16.3', 'flyway-test': '9.5.0', spring: '6.0.21', 'spring-boot': '3.1.12', 'zonky-postgres': 'default'], + [name: '9.22.3', flyway: '9.22.3', 'flyway-test': '9.5.0', spring: '6.1.15', 'spring-boot': '3.2.12', 'zonky-postgres': 'default'], + [name: '10.10.0', flyway: '10.10.0', 'flyway-test': '10.0.0', spring: '6.1.15', 'spring-boot': '3.3.6', 'zonky-postgres': 'default'], + [name: '10.20.1', flyway: '10.20.1', 'flyway-test': '10.0.0', spring: '6.2.0', 'spring-boot': '3.4.0', 'zonky-postgres': 'default'], + [name: '11.0.0', flyway: '11.0.0', 'flyway-test': '10.0.0', spring: '6.2.0', 'spring-boot': '3.4.0', 'zonky-postgres': 'default'] ] testSuites.find { it.name == 'liquibase' }.versions += [ [name: '4.17.2', liquibase: '4.17.2', spring: '6.0.14', 'spring-boot': '3.0.13'], - [name: '4.20.0', liquibase: '4.20.0', spring: '6.0.18', 'spring-boot': '3.1.10'], - [name: '4.24.0', liquibase: '4.24.0', spring: '6.1.5', 'spring-boot': '3.2.4'] + [name: '4.20.0', liquibase: '4.20.0', spring: '6.0.21', 'spring-boot': '3.1.12'], + [name: '4.24.0', liquibase: '4.24.0', spring: '6.1.15', 'spring-boot': '3.2.12'], + [name: '4.27.0', liquibase: '4.27.0', spring: '6.1.15', 'spring-boot': '3.3.6'], + [name: '4.29.2', liquibase: '4.29.2', spring: '6.2.0', 'spring-boot': '3.4.0'] ] } @@ -258,7 +262,7 @@ project(':embedded-database-spring-test') { api 'org.testcontainers:mysql:1.18.3' api 'org.testcontainers:mariadb:1.18.3' - optImplementation 'io.zonky.test:embedded-postgres:2.0.7' + optImplementation 'io.zonky.test:embedded-postgres:2.1.0' optImplementation 'com.opentable.components:otj-pg-embedded:0.13.4' optImplementation 'ru.yandex.qatools.embed:postgresql-embedded:2.10' @@ -319,7 +323,7 @@ project(':embedded-database-spring-test') { "testRuntimeClasspath_${suite.name}_${version.name}" { extendsFrom testRuntimeClasspath - if (version.flyway != null && version.flyway.startsWith('10.')) { + if (version.flyway != null && (version.flyway.startsWith('10.') || version.flyway.startsWith('11.'))) { dependencies.add(project.dependencies.create("org.flywaydb:flyway-database-postgresql:${version.flyway}")) } diff --git a/embedded-database-spring-test/src/main/java/io/zonky/test/db/flyway/FlywayWrapper.java b/embedded-database-spring-test/src/main/java/io/zonky/test/db/flyway/FlywayWrapper.java index 2675220..784ebbc 100644 --- a/embedded-database-spring-test/src/main/java/io/zonky/test/db/flyway/FlywayWrapper.java +++ b/embedded-database-spring-test/src/main/java/io/zonky/test/db/flyway/FlywayWrapper.java @@ -17,6 +17,7 @@ package io.zonky.test.db.flyway; import com.google.common.collect.ImmutableList; +import org.aopalliance.intercept.Interceptor; import org.aopalliance.intercept.MethodInterceptor; import org.flywaydb.core.Flyway; import org.flywaydb.core.api.resolver.MigrationResolver; @@ -113,8 +114,7 @@ public Collection getMigrations() { if (flywayVersion.isGreaterThanOrEqualTo("9")) { return invokeMethod(resolver, "resolveMigrations", config); } else if (flywayVersion.isGreaterThanOrEqualTo("5.2")) { - Class contextType = ClassUtils.forName("org.flywaydb.core.api.resolver.Context", classLoader); - Object contextInstance = ProxyFactory.getProxy(contextType, (MethodInterceptor) invocation -> + Object contextInstance = createMock("org.flywaydb.core.api.resolver.Context", (MethodInterceptor) invocation -> "getConfiguration".equals(invocation.getMethod().getName()) ? config : invocation.proceed()); return invokeMethod(resolver, "resolveMigrations", contextInstance); } else { @@ -126,7 +126,17 @@ public Collection getMigrations() { } private MigrationResolver createMigrationResolver(Flyway flyway) throws ClassNotFoundException { - if (flywayVersion.isGreaterThanOrEqualTo("8")) { + if (flywayVersion.isGreaterThanOrEqualTo("10.17.1")) { + Object executor = getField(flyway, "flywayExecutor"); + Object providers = invokeMethod(executor, "createResourceAndClassProviders", true); + Object resourceProvider = getField(providers, "left"); + Object classProvider = getField(providers, "right"); + Object sqlScript = createMock("org.flywaydb.core.internal.sqlscript.SqlScript", (MethodInterceptor) invocation -> false); + Object sqlScriptFactory = createMock("org.flywaydb.core.internal.sqlscript.SqlScriptFactory", (MethodInterceptor) invocation -> sqlScript); + Object sqlScriptExecutorFactory = createMock("org.flywaydb.core.internal.sqlscript.SqlScriptExecutorFactory"); + Object parsingContext = invokeConstructor("org.flywaydb.core.internal.parser.ParsingContext"); + return invokeMethod(executor, "createMigrationResolver", resourceProvider, classProvider, sqlScriptExecutorFactory, sqlScriptFactory, parsingContext, null); + } else if (flywayVersion.isGreaterThanOrEqualTo("8")) { Object executor = getField(flyway, "flywayExecutor"); Object providers = invokeMethod(executor, "createResourceAndClassProviders", true); Object resourceProvider = getField(providers, "left"); @@ -493,4 +503,9 @@ private static Object createMock(String className) throws ClassNotFoundException Class proxyInterface = ClassUtils.forName(className, classLoader); return ProxyFactory.getProxy(proxyInterface, (MethodInterceptor) invocation -> null); } + + private static Object createMock(String className, Interceptor interceptor) throws ClassNotFoundException { + Class proxyInterface = ClassUtils.forName(className, classLoader); + return ProxyFactory.getProxy(proxyInterface, interceptor); + } } diff --git a/embedded-database-spring-test/src/test/java/io/zonky/test/db/provider/OpenTableProviderWithConfigurationIntegrationTest.java b/embedded-database-spring-test/src/test/java/io/zonky/test/db/provider/OpenTableProviderWithConfigurationIntegrationTest.java index fc7c4ea..e122e78 100644 --- a/embedded-database-spring-test/src/test/java/io/zonky/test/db/provider/OpenTableProviderWithConfigurationIntegrationTest.java +++ b/embedded-database-spring-test/src/test/java/io/zonky/test/db/provider/OpenTableProviderWithConfigurationIntegrationTest.java @@ -19,6 +19,7 @@ import com.opentable.db.postgres.embedded.EmbeddedPostgres; import io.zonky.test.category.PostgresTestSuite; import io.zonky.test.db.AutoConfigureEmbeddedDatabase; +import io.zonky.test.support.TestSocketUtils; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; @@ -28,7 +29,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.util.SocketUtils; import javax.sql.DataSource; import java.sql.SQLException; @@ -49,7 +49,7 @@ static class Config { @Bean public Integer randomPort() { - return SocketUtils.findAvailableTcpPort(); + return TestSocketUtils.findAvailableTcpPort(); } @Bean diff --git a/embedded-database-spring-test/src/test/java/io/zonky/test/db/provider/ZonkyProviderWithConfigurationIntegrationTest.java b/embedded-database-spring-test/src/test/java/io/zonky/test/db/provider/ZonkyProviderWithConfigurationIntegrationTest.java index 22100fe..96a8712 100644 --- a/embedded-database-spring-test/src/test/java/io/zonky/test/db/provider/ZonkyProviderWithConfigurationIntegrationTest.java +++ b/embedded-database-spring-test/src/test/java/io/zonky/test/db/provider/ZonkyProviderWithConfigurationIntegrationTest.java @@ -19,6 +19,7 @@ import io.zonky.test.category.PostgresTestSuite; import io.zonky.test.db.AutoConfigureEmbeddedDatabase; import io.zonky.test.db.postgres.embedded.EmbeddedPostgres; +import io.zonky.test.support.TestSocketUtils; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; @@ -28,7 +29,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.util.SocketUtils; import javax.sql.DataSource; import java.sql.SQLException; @@ -49,7 +49,7 @@ static class Config { @Bean public Integer randomPort() { - return SocketUtils.findAvailableTcpPort(); + return TestSocketUtils.findAvailableTcpPort(); } @Bean diff --git a/embedded-database-spring-test/src/test/java/io/zonky/test/db/provider/postgres/OpenTablePostgresDatabaseProviderTest.java b/embedded-database-spring-test/src/test/java/io/zonky/test/db/provider/postgres/OpenTablePostgresDatabaseProviderTest.java index 406d161..8963c5b 100644 --- a/embedded-database-spring-test/src/test/java/io/zonky/test/db/provider/postgres/OpenTablePostgresDatabaseProviderTest.java +++ b/embedded-database-spring-test/src/test/java/io/zonky/test/db/provider/postgres/OpenTablePostgresDatabaseProviderTest.java @@ -20,6 +20,7 @@ import io.zonky.test.db.preparer.DatabasePreparer; import io.zonky.test.db.provider.support.BlockingDatabaseWrapper; import io.zonky.test.db.support.TestDatabasePreparer; +import io.zonky.test.support.TestSocketUtils; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -29,7 +30,6 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.mock.env.MockEnvironment; -import org.springframework.util.SocketUtils; import javax.sql.DataSource; import java.sql.SQLException; @@ -93,7 +93,7 @@ public void testGetDatabase() throws Exception { @Test public void testDatabaseCustomizers() throws Exception { - int randomPort = SocketUtils.findAvailableTcpPort(); + int randomPort = TestSocketUtils.findAvailableTcpPort(); when(databaseCustomizers.getIfAvailable()).thenReturn(Collections.singletonList(builder -> builder.setPort(randomPort))); DatabasePreparer preparer = TestDatabasePreparer.empty(); diff --git a/embedded-database-spring-test/src/test/java/io/zonky/test/db/provider/postgres/ZonkyPostgresDatabaseProviderTest.java b/embedded-database-spring-test/src/test/java/io/zonky/test/db/provider/postgres/ZonkyPostgresDatabaseProviderTest.java index 47e0208..53a6c6b 100644 --- a/embedded-database-spring-test/src/test/java/io/zonky/test/db/provider/postgres/ZonkyPostgresDatabaseProviderTest.java +++ b/embedded-database-spring-test/src/test/java/io/zonky/test/db/provider/postgres/ZonkyPostgresDatabaseProviderTest.java @@ -20,6 +20,7 @@ import io.zonky.test.db.preparer.DatabasePreparer; import io.zonky.test.db.provider.support.BlockingDatabaseWrapper; import io.zonky.test.db.support.TestDatabasePreparer; +import io.zonky.test.support.TestSocketUtils; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -29,7 +30,6 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.mock.env.MockEnvironment; -import org.springframework.util.SocketUtils; import javax.sql.DataSource; import java.sql.SQLException; @@ -93,7 +93,7 @@ public void testGetDatabase() throws Exception { @Test public void testDatabaseCustomizers() throws Exception { - int randomPort = SocketUtils.findAvailableTcpPort(); + int randomPort = TestSocketUtils.findAvailableTcpPort(); when(databaseCustomizers.getIfAvailable()).thenReturn(Collections.singletonList(builder -> builder.setPort(randomPort))); DatabasePreparer preparer = TestDatabasePreparer.empty(); diff --git a/embedded-database-spring-test/src/test/java/io/zonky/test/support/TestSocketUtils.java b/embedded-database-spring-test/src/test/java/io/zonky/test/support/TestSocketUtils.java new file mode 100644 index 0000000..eeb8e42 --- /dev/null +++ b/embedded-database-spring-test/src/test/java/io/zonky/test/support/TestSocketUtils.java @@ -0,0 +1,62 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.zonky.test.support; + +import org.springframework.util.Assert; + +import javax.net.ServerSocketFactory; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.util.Random; + +public class TestSocketUtils { + + private static final int PORT_RANGE_MIN = 1024; + private static final int PORT_RANGE_MAX = 65535; + private static final int PORT_RANGE_PLUS_ONE = PORT_RANGE_MAX - PORT_RANGE_MIN + 1; + private static final int MAX_ATTEMPTS = 1_000; + + private static final Random random = new Random(System.nanoTime()); + + private TestSocketUtils() {} + + public static int findAvailableTcpPort() { + int candidatePort; + int searchCounter = 0; + do { + Assert.state(++searchCounter <= MAX_ATTEMPTS, () -> String.format( + "Could not find an available TCP port in the range [%d, %d] after %d attempts", + PORT_RANGE_MIN, PORT_RANGE_MAX, MAX_ATTEMPTS)); + candidatePort = PORT_RANGE_MIN + random.nextInt(PORT_RANGE_PLUS_ONE); + } + while (!isPortAvailable(candidatePort)); + + return candidatePort; + } + + private static boolean isPortAvailable(int port) { + try { + ServerSocket serverSocket = ServerSocketFactory.getDefault() + .createServerSocket(port, 1, InetAddress.getByName("localhost")); + serverSocket.close(); + return true; + } + catch (Exception ex) { + return false; + } + } +}