From 591b19b5a09f9be9dd7390d54112af4bd6142f7a Mon Sep 17 00:00:00 2001 From: tsohnugo Date: Mon, 14 Apr 2025 18:09:24 +0800 Subject: [PATCH 1/5] GaussDB is adapted to testcontainers --- modules/gaussdb/build.gradle | 16 ++ .../containers/GaussDBContainer.java | 152 ++++++++++++++++++ .../containers/GaussDBContainerProvider.java | 34 ++++ ...s.containers.JdbcDatabaseContainerProvider | 1 + .../org/testcontainers/GaussDBTestImages.java | 7 + .../containers/GaussDBConnectionURLTest.java | 84 ++++++++++ .../jdbc/DatabaseDriverShutdownTest.java | 52 ++++++ .../jdbc/DatabaseDriverTmpfsTest.java | 37 +++++ .../jdbc/gaussdb/GaussDBJDBCDriverTest.java | 24 +++ .../gaussdb/CustomizableGaussDBTest.java | 37 +++++ .../junit/gaussdb/SimpleGaussDBTest.java | 130 +++++++++++++++ .../src/test/resources/logback-test.xml | 16 ++ .../test/resources/somepath/init_gaussdb.sql | 5 + .../resources/somepath/init_gaussdb_2.sql | 5 + 14 files changed, 600 insertions(+) create mode 100644 modules/gaussdb/build.gradle create mode 100644 modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainer.java create mode 100644 modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainerProvider.java create mode 100644 modules/gaussdb/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider create mode 100644 modules/gaussdb/src/test/java/org/testcontainers/GaussDBTestImages.java create mode 100644 modules/gaussdb/src/test/java/org/testcontainers/containers/GaussDBConnectionURLTest.java create mode 100644 modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverShutdownTest.java create mode 100644 modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverTmpfsTest.java create mode 100644 modules/gaussdb/src/test/java/org/testcontainers/jdbc/gaussdb/GaussDBJDBCDriverTest.java create mode 100644 modules/gaussdb/src/test/java/org/testcontainers/junit/gaussdb/CustomizableGaussDBTest.java create mode 100644 modules/gaussdb/src/test/java/org/testcontainers/junit/gaussdb/SimpleGaussDBTest.java create mode 100644 modules/gaussdb/src/test/resources/logback-test.xml create mode 100644 modules/gaussdb/src/test/resources/somepath/init_gaussdb.sql create mode 100644 modules/gaussdb/src/test/resources/somepath/init_gaussdb_2.sql diff --git a/modules/gaussdb/build.gradle b/modules/gaussdb/build.gradle new file mode 100644 index 00000000000..5f932ebff06 --- /dev/null +++ b/modules/gaussdb/build.gradle @@ -0,0 +1,16 @@ +description = "Testcontainers :: JDBC :: GaussDB" + +dependencies { + api project(':jdbc') + + compileOnly project(':r2dbc') +// compileOnly 'io.r2dbc:r2dbc-postgresql:0.8.13.RELEASE' + + testImplementation project(':jdbc-test') + testRuntimeOnly 'com.huaweicloud.gaussdb:gaussdbjdbc:506.0.0.b058' + +// testImplementation testFixtures(project(':r2dbc')) +// testRuntimeOnly 'io.r2dbc:r2dbc-postgresql:0.8.13.RELEASE' + + compileOnly 'org.jetbrains:annotations:24.1.0' +} diff --git a/modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainer.java b/modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainer.java new file mode 100644 index 00000000000..77121d35cb0 --- /dev/null +++ b/modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainer.java @@ -0,0 +1,152 @@ +package org.testcontainers.containers; + +import org.jetbrains.annotations.NotNull; +import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; +import org.testcontainers.utility.DockerImageName; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.Set; + +/** + * Testcontainers implementation for PostgreSQL. + *

+ * Supported images: {@code postgres}, {@code pgvector/pgvector} + *

+ * Exposed ports: 5432 + */ +public class GaussDBContainer> extends JdbcDatabaseContainer { + + public static final String NAME = "gaussdb"; + + public static final String IMAGE = "opengauss/opengauss"; + + public static final String DEFAULT_TAG = "latest"; + + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("opengauss/opengauss").asCompatibleSubstituteFor("gaussdb"); + + public static final Integer GaussDB_PORT = 5432; + + static final String DEFAULT_USER = "gaussdb"; + + static final String DEFAULT_PASSWORD = "Enmo@123"; + + private String databaseName = "postgres"; + + private String username = "gaussdb"; + + private String password = "Enmo@123"; + + private static final String FSYNC_OFF_OPTION = "fsync=off"; + + /** + * @deprecated use {@link #GaussDBContainer(DockerImageName)} or {@link #GaussDBContainer(String)} instead + */ + @Deprecated + public GaussDBContainer() { + this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); + } + + public GaussDBContainer(final String dockerImageName) { + this(DockerImageName.parse(dockerImageName)); + } + + public GaussDBContainer(final DockerImageName dockerImageName) { + super(dockerImageName); + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + this.withEnv("GS_PASSWORD", "Enmo@123") + .withDatabaseName("postgres"); + // Comment out the test error code for the time being +// this.waitStrategy +// = +// new LogMessageWaitStrategy() +// .withRegEx(".*can not read GAUSS_WARNING_TYPE env.*\\s") +// .withTimes(1) +// .withStartupTimeout(Duration.of(60, ChronoUnit.SECONDS)); +// this.setCommand("-c", FSYNC_OFF_OPTION); + + addExposedPort(GaussDB_PORT); + } + + /** + * @return the ports on which to check if the container is ready + * @deprecated use {@link #getLivenessCheckPortNumbers()} instead + */ + @NotNull + @Override + @Deprecated + protected Set getLivenessCheckPorts() { + return super.getLivenessCheckPorts(); + } + + @Override + protected void configure() { + // Disable Postgres driver use of java.util.logging to reduce noise at startup time + withUrlParam("loggerLevel", "OFF"); + addEnv("POSTGRES_DB", databaseName); + addEnv("POSTGRES_USER", username); + addEnv("POSTGRES_PASSWORD", password); + } + + @Override + public String getDriverClassName() { + return "com.huawei.gaussdb.jdbc.Driver"; + } + + @Override + public String getJdbcUrl() { + String additionalUrlParams = constructUrlParameters("?", "&"); + return ( + "jdbc:gaussdb://" + + getHost() + + ":" + + getMappedPort(GaussDB_PORT) + + "/" + + databaseName + + additionalUrlParams + ); + } + + @Override + public String getDatabaseName() { + return databaseName; + } + + @Override + public String getUsername() { + return username; + } + + @Override + public String getPassword() { + return password; + } + + @Override + public String getTestQueryString() { + return "SELECT 1"; + } + + @Override + public SELF withDatabaseName(final String databaseName) { + this.databaseName = databaseName; + return self(); + } + + @Override + public SELF withUsername(final String username) { + this.username = username; + return self(); + } + + @Override + public SELF withPassword(final String password) { + this.password = password; + return self(); + } + + @Override + protected void waitUntilContainerStarted() { + getWaitStrategy().waitUntilReady(this); + } +} diff --git a/modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainerProvider.java b/modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainerProvider.java new file mode 100644 index 00000000000..e21aebb85ec --- /dev/null +++ b/modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainerProvider.java @@ -0,0 +1,34 @@ +package org.testcontainers.containers; + +import org.testcontainers.jdbc.ConnectionUrl; +import org.testcontainers.utility.DockerImageName; + +/** + * Factory for GaussDB containers. + */ +public class GaussDBContainerProvider extends JdbcDatabaseContainerProvider { + + public static final String USER_PARAM = "user"; + + public static final String PASSWORD_PARAM = "password"; + + @Override + public boolean supports(String databaseType) { + return databaseType.equals(GaussDBContainer.NAME); + } + + @Override + public JdbcDatabaseContainer newInstance() { + return newInstance(GaussDBContainer.DEFAULT_TAG); + } + + @Override + public JdbcDatabaseContainer newInstance(String tag) { + return new GaussDBContainer(DockerImageName.parse(GaussDBContainer.IMAGE).withTag(tag)); + } + + @Override + public JdbcDatabaseContainer newInstance(ConnectionUrl connectionUrl) { + return newInstanceFromConnectionUrl(connectionUrl, USER_PARAM, PASSWORD_PARAM); + } +} diff --git a/modules/gaussdb/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider b/modules/gaussdb/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider new file mode 100644 index 00000000000..9707299c2ba --- /dev/null +++ b/modules/gaussdb/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider @@ -0,0 +1 @@ +org.testcontainers.containers.GaussDBContainerProvider diff --git a/modules/gaussdb/src/test/java/org/testcontainers/GaussDBTestImages.java b/modules/gaussdb/src/test/java/org/testcontainers/GaussDBTestImages.java new file mode 100644 index 00000000000..fbc1dbd5e62 --- /dev/null +++ b/modules/gaussdb/src/test/java/org/testcontainers/GaussDBTestImages.java @@ -0,0 +1,7 @@ +package org.testcontainers; + +import org.testcontainers.utility.DockerImageName; + +public interface GaussDBTestImages { + DockerImageName GAUSSDB_TEST_IMAGE = DockerImageName.parse("opengauss/opengauss:latest").asCompatibleSubstituteFor("gaussdb"); +} diff --git a/modules/gaussdb/src/test/java/org/testcontainers/containers/GaussDBConnectionURLTest.java b/modules/gaussdb/src/test/java/org/testcontainers/containers/GaussDBConnectionURLTest.java new file mode 100644 index 00000000000..9db9b3fe38e --- /dev/null +++ b/modules/gaussdb/src/test/java/org/testcontainers/containers/GaussDBConnectionURLTest.java @@ -0,0 +1,84 @@ +package org.testcontainers.containers; + +import org.junit.Test; +import org.testcontainers.GaussDBTestImages; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; + +public class GaussDBConnectionURLTest { + + @Test + public void shouldCorrectlyAppendQueryString() { + GaussDBContainer postgres = new FixedJdbcUrlPostgreSQLContainer(); + String connectionUrl = postgres.constructUrlForConnection("?stringtype=unspecified&stringtype=unspecified"); + String queryString = connectionUrl.substring(connectionUrl.indexOf('?')); + + assertThat(queryString) + .as("Query String contains expected params") + .contains("?stringtype=unspecified&stringtype=unspecified"); + assertThat(queryString.indexOf('?')).as("Query String starts with '?'").isZero(); + assertThat(queryString.substring(1)).as("Query String does not contain extra '?'").doesNotContain("?"); + } + + @Test + public void shouldCorrectlyAppendQueryStringWhenNoBaseParams() { + GaussDBContainer postgres = new NoParamsUrlPostgreSQLContainer(); + String connectionUrl = postgres.constructUrlForConnection("?stringtype=unspecified&stringtype=unspecified"); + String queryString = connectionUrl.substring(connectionUrl.indexOf('?')); + + assertThat(queryString) + .as("Query String contains expected params") + .contains("?stringtype=unspecified&stringtype=unspecified"); + assertThat(queryString.indexOf('?')).as("Query String starts with '?'").isZero(); + assertThat(queryString.substring(1)).as("Query String does not contain extra '?'").doesNotContain("?"); + } + + @Test + public void shouldReturnOriginalURLWhenEmptyQueryString() { + GaussDBContainer postgres = new FixedJdbcUrlPostgreSQLContainer(); + String connectionUrl = postgres.constructUrlForConnection(""); + + assertThat(postgres.getJdbcUrl()).as("Query String remains unchanged").isEqualTo(connectionUrl); + } + + @Test + public void shouldRejectInvalidQueryString() { + assertThat( + catchThrowable(() -> { + new NoParamsUrlPostgreSQLContainer().constructUrlForConnection("stringtype=unspecified"); + }) + ) + .as("Fails when invalid query string provided") + .isInstanceOf(IllegalArgumentException.class); + } + + static class FixedJdbcUrlPostgreSQLContainer extends GaussDBContainer { + + public FixedJdbcUrlPostgreSQLContainer() { + super(GaussDBTestImages.GAUSSDB_TEST_IMAGE); + } + + @Override + public String getHost() { + return "localhost"; + } + + @Override + public Integer getMappedPort(int originalPort) { + return 34532; + } + } + + static class NoParamsUrlPostgreSQLContainer extends GaussDBContainer { + + public NoParamsUrlPostgreSQLContainer() { + super(GaussDBTestImages.GAUSSDB_TEST_IMAGE); + } + + @Override + public String getJdbcUrl() { + return "jdbc:postgresql://host:port/database"; + } + } +} diff --git a/modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverShutdownTest.java b/modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverShutdownTest.java new file mode 100644 index 00000000000..803f0795a3e --- /dev/null +++ b/modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverShutdownTest.java @@ -0,0 +1,52 @@ +package org.testcontainers.jdbc; + +import org.junit.AfterClass; +import org.junit.Test; +import org.testcontainers.containers.JdbcDatabaseContainer; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * This test belongs in the jdbc module, as it is focused on testing the behaviour of {@link org.testcontainers.containers.JdbcDatabaseContainer}. + * However, the need to use the {@link org.testcontainers.containers.GaussDBContainerProvider} (due to the jdbc:tc:postgresql) URL forces it to live here in + * the mysql module, to avoid circular dependencies. + * TODO: Move to the jdbc module and either (a) implement a barebones {@link org.testcontainers.containers.JdbcDatabaseContainerProvider} for testing, or (b) refactor into a unit test. + */ +public class DatabaseDriverShutdownTest { + + @AfterClass + public static void testCleanup() { + ContainerDatabaseDriver.killContainers(); + } + + @Test + public void shouldStopContainerWhenAllConnectionsClosed() throws SQLException { + final String jdbcUrl = "jdbc:tc:gaussdb://hostname/databasename"; + + getConnectionAndClose(jdbcUrl); + + JdbcDatabaseContainer container = ContainerDatabaseDriver.getContainer(jdbcUrl); + assertThat(container).as("Database container instance is null as expected").isNull(); + } + + @Test + public void shouldNotStopDaemonContainerWhenAllConnectionsClosed() throws SQLException { + final String jdbcUrl = "jdbc:tc:gaussdb://hostname/databasename?TC_DAEMON=true"; + + getConnectionAndClose(jdbcUrl); + + JdbcDatabaseContainer container = ContainerDatabaseDriver.getContainer(jdbcUrl); + assertThat(container).as("Database container instance is not null as expected").isNotNull(); + assertThat(container.isRunning()).as("Database container is running as expected").isTrue(); + } + + private void getConnectionAndClose(String jdbcUrl) throws SQLException { + try (Connection connection = DriverManager.getConnection(jdbcUrl)) { + assertThat(connection).as("Obtained connection as expected").isNotNull(); + } + } +} diff --git a/modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverTmpfsTest.java b/modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverTmpfsTest.java new file mode 100644 index 00000000000..48d2d43386c --- /dev/null +++ b/modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverTmpfsTest.java @@ -0,0 +1,37 @@ +package org.testcontainers.jdbc; + +import org.junit.Test; +import org.testcontainers.containers.Container; +import org.testcontainers.containers.JdbcDatabaseContainer; + +import java.sql.Connection; +import java.sql.DriverManager; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * This test belongs in the jdbc module, as it is focused on testing the behaviour of {@link org.testcontainers.containers.JdbcDatabaseContainer}. + * However, the need to use the {@link org.testcontainers.containers.GaussDBContainerProvider} (due to the jdbc:tc:postgresql) URL forces it to live here in + * the mysql module, to avoid circular dependencies. + * TODO: Move to the jdbc module and either (a) implement a barebones {@link org.testcontainers.containers.JdbcDatabaseContainerProvider} for testing, or (b) refactor into a unit test. + */ +public class DatabaseDriverTmpfsTest { + + @Test + public void testDatabaseHasTmpFsViaConnectionString() throws Exception { + final String jdbcUrl = "jdbc:tc:gaussdb://hostname/databasename?TC_TMPFS=/testtmpfs:rw"; + try (Connection ignored = DriverManager.getConnection(jdbcUrl)) { + JdbcDatabaseContainer container = ContainerDatabaseDriver.getContainer(jdbcUrl); + // check file doesn't exist + String path = "/testtmpfs/test.file"; + Container.ExecResult execResult = container.execInContainer("ls", path); + assertThat(execResult.getExitCode()) + .as("tmpfs inside container doesn't have file that doesn't exist") + .isNotZero(); + // touch && check file does exist + container.execInContainer("touch", path); + execResult = container.execInContainer("ls", path); + assertThat(execResult.getExitCode()).as("tmpfs inside container has file that does exist").isZero(); + } + } +} diff --git a/modules/gaussdb/src/test/java/org/testcontainers/jdbc/gaussdb/GaussDBJDBCDriverTest.java b/modules/gaussdb/src/test/java/org/testcontainers/jdbc/gaussdb/GaussDBJDBCDriverTest.java new file mode 100644 index 00000000000..7fc88226844 --- /dev/null +++ b/modules/gaussdb/src/test/java/org/testcontainers/jdbc/gaussdb/GaussDBJDBCDriverTest.java @@ -0,0 +1,24 @@ +package org.testcontainers.jdbc.gaussdb; + +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.testcontainers.jdbc.AbstractJDBCDriverTest; + +import java.util.Arrays; +import java.util.EnumSet; + +@RunWith(Parameterized.class) +public class GaussDBJDBCDriverTest extends AbstractJDBCDriverTest { + + @Parameterized.Parameters(name = "{index} - {0}") + public static Iterable data() { + return Arrays.asList( + new Object[][] { + { + "jdbc:tc:gaussdb:///postgres?user=gaussdb&password=Enmo@123", + EnumSet.of(Options.JDBCParams), + }, + } + ); + } +} diff --git a/modules/gaussdb/src/test/java/org/testcontainers/junit/gaussdb/CustomizableGaussDBTest.java b/modules/gaussdb/src/test/java/org/testcontainers/junit/gaussdb/CustomizableGaussDBTest.java new file mode 100644 index 00000000000..cd5dc5a7cae --- /dev/null +++ b/modules/gaussdb/src/test/java/org/testcontainers/junit/gaussdb/CustomizableGaussDBTest.java @@ -0,0 +1,37 @@ +package org.testcontainers.junit.gaussdb; + +import org.junit.Test; +import org.testcontainers.GaussDBTestImages; +import org.testcontainers.containers.GaussDBContainer; +import org.testcontainers.db.AbstractContainerDatabaseTest; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CustomizableGaussDBTest extends AbstractContainerDatabaseTest { + + private static final String DB_NAME = "foo"; + + private static final String USER = "bar"; + + private static final String PWD = "GaussDB@123"; + + @Test + public void testSimple() throws SQLException { + try ( + GaussDBContainer postgres = new GaussDBContainer<>(GaussDBTestImages.GAUSSDB_TEST_IMAGE) + .withDatabaseName(DB_NAME) + .withUsername(USER) + .withPassword(PWD) + ) { + postgres.start(); + + ResultSet resultSet = performQuery(postgres, "SELECT 1"); + + int resultSetInt = resultSet.getInt(1); + assertThat(resultSetInt).as("A basic SELECT query succeeds").isEqualTo(1); + } + } +} diff --git a/modules/gaussdb/src/test/java/org/testcontainers/junit/gaussdb/SimpleGaussDBTest.java b/modules/gaussdb/src/test/java/org/testcontainers/junit/gaussdb/SimpleGaussDBTest.java new file mode 100644 index 00000000000..d4794b42620 --- /dev/null +++ b/modules/gaussdb/src/test/java/org/testcontainers/junit/gaussdb/SimpleGaussDBTest.java @@ -0,0 +1,130 @@ +package org.testcontainers.junit.gaussdb; + +import org.junit.Test; +import org.testcontainers.GaussDBTestImages; +import org.testcontainers.containers.GaussDBContainer; +import org.testcontainers.db.AbstractContainerDatabaseTest; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.logging.Level; +import java.util.logging.LogManager; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; + +public class SimpleGaussDBTest extends AbstractContainerDatabaseTest { + static { + // Gaussdb JDBC driver uses JUL; disable it to avoid annoying, irrelevant, stderr logs during connection testing + LogManager.getLogManager().getLogger("").setLevel(Level.OFF); + } + + @Test + public void testSimple() throws SQLException { + try (GaussDBContainer gaussdb = new GaussDBContainer<>(GaussDBTestImages.GAUSSDB_TEST_IMAGE) + .withEnv("GS_PASSWORD", "Enmo@123")) { + gaussdb.start(); + System.out.println(gaussdb.getLogs()); + + ResultSet resultSet = performQuery(gaussdb, "SELECT 1"); + int resultSetInt = resultSet.getInt(1); + assertThat(resultSetInt).as("A basic SELECT query succeeds").isEqualTo(1); + assertHasCorrectExposedAndLivenessCheckPorts(gaussdb); + } + } + + @Test + public void testCommandOverride() throws SQLException { + try ( + GaussDBContainer gauss = new GaussDBContainer<>(GaussDBTestImages.GAUSSDB_TEST_IMAGE) +// .withCommand("gaussdb", "-c", "max_connections=42") + ) { + gauss.start(); + + ResultSet resultSet = performQuery(gauss, "SELECT current_setting('max_connections')"); + String result = resultSet.getString(1); + assertThat(result).as("max_connections should be overridden").isEqualTo("42"); + } + } + + @Test + public void testUnsetCommand() throws SQLException { + try ( + GaussDBContainer gaussdb = new GaussDBContainer<>(GaussDBTestImages.GAUSSDB_TEST_IMAGE) + .withCommand("gaussdb -c max_connections=42") + .withCommand() + ) { + gaussdb.start(); + + ResultSet resultSet = performQuery(gaussdb, "SELECT current_setting('max_connections')"); + String result = resultSet.getString(1); + assertThat(result).as("max_connections should not be overridden").isNotEqualTo("42"); + } + } + + @Test + public void testMissingInitScript() { + try ( + GaussDBContainer gaussdb = new GaussDBContainer<>(GaussDBTestImages.GAUSSDB_TEST_IMAGE) + .withInitScript(null) + ) { + assertThatNoException().isThrownBy(gaussdb::start); + } + } + + @Test + public void testExplicitInitScript() throws SQLException { + try ( + GaussDBContainer gaussdb = new GaussDBContainer<>(GaussDBTestImages.GAUSSDB_TEST_IMAGE) + .withInitScript("somepath/init_gaussdb.sql") + ) { + gaussdb.start(); + + ResultSet resultSet = performQuery(gaussdb, "SELECT foo FROM bar"); + + String firstColumnValue = resultSet.getString(1); + assertThat(firstColumnValue).as("Value from init script should equal real value").isEqualTo("hello world"); + } + } + + @Test + public void testExplicitInitScripts() throws SQLException { + try ( + GaussDBContainer gaussdb = new GaussDBContainer<>(GaussDBTestImages.GAUSSDB_TEST_IMAGE) + .withInitScripts("somepath/init_gaussdb.sql", "somepath/init_gaussdb_2.sql") + ) { + gaussdb.start(); + + ResultSet resultSet = performQuery( + gaussdb, + "SELECT foo AS value FROM bar UNION SELECT bar AS value FROM foo" + ); + + String columnValue1 = resultSet.getString(1); + resultSet.next(); + String columnValue2 = resultSet.getString(1); + assertThat(columnValue1).as("Value from init script 1 should equal real value").isEqualTo("hello world"); + assertThat(columnValue2).as("Value from init script 2 should equal real value").isEqualTo("hello world 2"); + } + } + + @Test + public void testWithAdditionalUrlParamInJdbcUrl() { + try ( + GaussDBContainer gaussdb = new GaussDBContainer<>(GaussDBTestImages.GAUSSDB_TEST_IMAGE) + .withUrlParam("charSet", "UNICODE") + ) { + gaussdb.start(); + String jdbcUrl = gaussdb.getJdbcUrl(); + assertThat(jdbcUrl).contains("?"); + assertThat(jdbcUrl).contains("&"); + assertThat(jdbcUrl).contains("charSet=UNICODE"); + } + } + + private void assertHasCorrectExposedAndLivenessCheckPorts(GaussDBContainer gaussdb) { + assertThat(gaussdb.getExposedPorts()).containsExactly(GaussDBContainer.GaussDB_PORT); + assertThat(gaussdb.getLivenessCheckPortNumbers()) + .containsExactly(gaussdb.getMappedPort(GaussDBContainer.GaussDB_PORT)); + } +} diff --git a/modules/gaussdb/src/test/resources/logback-test.xml b/modules/gaussdb/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..83ef7a1a3ef --- /dev/null +++ b/modules/gaussdb/src/test/resources/logback-test.xml @@ -0,0 +1,16 @@ + + + + + + %d{HH:mm:ss.SSS} %-5level %logger - %msg%n + + + + + + + + + diff --git a/modules/gaussdb/src/test/resources/somepath/init_gaussdb.sql b/modules/gaussdb/src/test/resources/somepath/init_gaussdb.sql new file mode 100644 index 00000000000..2b00ee968b0 --- /dev/null +++ b/modules/gaussdb/src/test/resources/somepath/init_gaussdb.sql @@ -0,0 +1,5 @@ +CREATE TABLE bar ( + foo VARCHAR(255) +); + +INSERT INTO bar (foo) VALUES ('hello world'); \ No newline at end of file diff --git a/modules/gaussdb/src/test/resources/somepath/init_gaussdb_2.sql b/modules/gaussdb/src/test/resources/somepath/init_gaussdb_2.sql new file mode 100644 index 00000000000..f4ecf9bbfad --- /dev/null +++ b/modules/gaussdb/src/test/resources/somepath/init_gaussdb_2.sql @@ -0,0 +1,5 @@ +CREATE TABLE foo ( + bar VARCHAR(255) +); + +INSERT INTO foo (bar) VALUES ('hello world 2'); From 0449996ffba55016f3ff6b32fe450e81ad3773d7 Mon Sep 17 00:00:00 2001 From: tsohnugo Date: Tue, 22 Apr 2025 17:56:54 +0800 Subject: [PATCH 2/5] Add gaussdb support (still WIP) --- docs/modules/databases/gaussdb.md | 33 +++++++++ modules/gaussdb/build.gradle | 6 -- .../containers/GaussDBContainer.java | 73 +++++++++++-------- .../org/testcontainers/GaussDBTestImages.java | 2 +- .../containers/GaussDBConnectionURLTest.java | 26 +++---- .../jdbc/DatabaseDriverShutdownTest.java | 2 +- .../jdbc/DatabaseDriverTmpfsTest.java | 2 +- .../jdbc/gaussdb/GaussDBJDBCDriverTest.java | 2 +- .../gaussdb/CustomizableGaussDBTest.java | 7 +- .../junit/gaussdb/SimpleGaussDBTest.java | 8 +- .../jdbc/AbstractJDBCDriverTest.java | 3 +- 11 files changed, 100 insertions(+), 64 deletions(-) create mode 100644 docs/modules/databases/gaussdb.md diff --git a/docs/modules/databases/gaussdb.md b/docs/modules/databases/gaussdb.md new file mode 100644 index 00000000000..5b066963157 --- /dev/null +++ b/docs/modules/databases/gaussdb.md @@ -0,0 +1,33 @@ +# GaussDB Module + +See [Database containers](./index.md) for documentation and usage that is common to all relational database container types. + +## Usage example + +You can use 'GaussDBContainer' like any other JDBC container: + +[Container creation](../../../modules/gaussdb/src/test/java/org/testcontainers/junit/gaussdb/SimpleGaussDBTest.java) inside_block:constructor + + +## Adding this module to your project dependencies + +Add the following dependency to your `pom.xml`/`build.gradle` file: + +=== "Gradle" + ```groovy + testImplementation "org.testcontainers:gaussdb:{{latest_version}}" + ``` +=== "Maven" + ```xml + + org.testcontainers + gaussdb + {{latest_version}} + test + + ``` + +!!! hint + Adding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency. + + diff --git a/modules/gaussdb/build.gradle b/modules/gaussdb/build.gradle index 5f932ebff06..5c72a4dd20a 100644 --- a/modules/gaussdb/build.gradle +++ b/modules/gaussdb/build.gradle @@ -3,14 +3,8 @@ description = "Testcontainers :: JDBC :: GaussDB" dependencies { api project(':jdbc') - compileOnly project(':r2dbc') -// compileOnly 'io.r2dbc:r2dbc-postgresql:0.8.13.RELEASE' - testImplementation project(':jdbc-test') testRuntimeOnly 'com.huaweicloud.gaussdb:gaussdbjdbc:506.0.0.b058' -// testImplementation testFixtures(project(':r2dbc')) -// testRuntimeOnly 'io.r2dbc:r2dbc-postgresql:0.8.13.RELEASE' - compileOnly 'org.jetbrains:annotations:24.1.0' } diff --git a/modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainer.java b/modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainer.java index 77121d35cb0..df95e10e346 100644 --- a/modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainer.java +++ b/modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainer.java @@ -1,19 +1,21 @@ package org.testcontainers.containers; import org.jetbrains.annotations.NotNull; -import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.containers.wait.strategy.WaitStrategy; +import org.testcontainers.containers.wait.strategy.WaitStrategyTarget; import org.testcontainers.utility.DockerImageName; import java.time.Duration; -import java.time.temporal.ChronoUnit; import java.util.Set; +import java.util.concurrent.TimeUnit; /** - * Testcontainers implementation for PostgreSQL. + * Testcontainers implementation for GaussDB. *

- * Supported images: {@code postgres}, {@code pgvector/pgvector} + * Supported images: {@code opengauss/opengauss} *

- * Exposed ports: 5432 + * Exposed ports: 8000 */ public class GaussDBContainer> extends JdbcDatabaseContainer { @@ -21,23 +23,22 @@ public class GaussDBContainer> extends JdbcD public static final String IMAGE = "opengauss/opengauss"; - public static final String DEFAULT_TAG = "latest"; + public static final String DEFAULT_TAG = "7.0.0-RC1.B023"; - private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("opengauss/opengauss").asCompatibleSubstituteFor("gaussdb"); + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(IMAGE); - public static final Integer GaussDB_PORT = 5432; + public static final Integer GaussDB_PORT = 8000; - static final String DEFAULT_USER = "gaussdb"; + public static final String DEFAULT_USER_NAME = "test"; - static final String DEFAULT_PASSWORD = "Enmo@123"; + // At least one uppercase, lowercase, numeric, special character, and password length(8). + public static final String DEFAULT_PASSWORD = "Test@123"; - private String databaseName = "postgres"; + private String databaseName = "gaussdb"; - private String username = "gaussdb"; + private String username = DEFAULT_USER_NAME; - private String password = "Enmo@123"; - - private static final String FSYNC_OFF_OPTION = "fsync=off"; + private String password = DEFAULT_PASSWORD; /** * @deprecated use {@link #GaussDBContainer(DockerImageName)} or {@link #GaussDBContainer(String)} instead @@ -53,19 +54,25 @@ public GaussDBContainer(final String dockerImageName) { public GaussDBContainer(final DockerImageName dockerImageName) { super(dockerImageName); - dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); - this.withEnv("GS_PASSWORD", "Enmo@123") - .withDatabaseName("postgres"); - // Comment out the test error code for the time being -// this.waitStrategy -// = -// new LogMessageWaitStrategy() -// .withRegEx(".*can not read GAUSS_WARNING_TYPE env.*\\s") -// .withTimes(1) -// .withStartupTimeout(Duration.of(60, ChronoUnit.SECONDS)); -// this.setCommand("-c", FSYNC_OFF_OPTION); - - addExposedPort(GaussDB_PORT); + setWaitStrategy(new WaitStrategy() { + @Override + public void waitUntilReady(WaitStrategyTarget waitStrategyTarget) { + Wait.forListeningPort().waitUntilReady(waitStrategyTarget); + try { + // Open Gauss will set up users and password when ports are ready. + Wait.forLogMessage(".*gs_ctl stopped.*", 1).waitUntilReady(waitStrategyTarget); + // Not enough and no idea + TimeUnit.SECONDS.sleep(3); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + @Override + public WaitStrategy withStartupTimeout(Duration duration) { + return GenericContainer.DEFAULT_WAIT_STRATEGY.withStartupTimeout(duration); + } + }); } /** @@ -81,11 +88,13 @@ protected Set getLivenessCheckPorts() { @Override protected void configure() { - // Disable Postgres driver use of java.util.logging to reduce noise at startup time + // Disable GaussDB driver use of java.util.logging to reduce noise at startup time withUrlParam("loggerLevel", "OFF"); - addEnv("POSTGRES_DB", databaseName); - addEnv("POSTGRES_USER", username); - addEnv("POSTGRES_PASSWORD", password); + addExposedPorts(GaussDB_PORT); + addEnv("GS_DB", databaseName); + addEnv("GS_PORT", String.valueOf(GaussDB_PORT)); + addEnv("GS_USERNAME", username); + addEnv("GS_PASSWORD", password); } @Override diff --git a/modules/gaussdb/src/test/java/org/testcontainers/GaussDBTestImages.java b/modules/gaussdb/src/test/java/org/testcontainers/GaussDBTestImages.java index fbc1dbd5e62..25d0975fd60 100644 --- a/modules/gaussdb/src/test/java/org/testcontainers/GaussDBTestImages.java +++ b/modules/gaussdb/src/test/java/org/testcontainers/GaussDBTestImages.java @@ -3,5 +3,5 @@ import org.testcontainers.utility.DockerImageName; public interface GaussDBTestImages { - DockerImageName GAUSSDB_TEST_IMAGE = DockerImageName.parse("opengauss/opengauss:latest").asCompatibleSubstituteFor("gaussdb"); + DockerImageName GAUSSDB_TEST_IMAGE = DockerImageName.parse("opengauss/opengauss:7.0.0-RC1.B023"); } diff --git a/modules/gaussdb/src/test/java/org/testcontainers/containers/GaussDBConnectionURLTest.java b/modules/gaussdb/src/test/java/org/testcontainers/containers/GaussDBConnectionURLTest.java index 9db9b3fe38e..31a6f93f189 100644 --- a/modules/gaussdb/src/test/java/org/testcontainers/containers/GaussDBConnectionURLTest.java +++ b/modules/gaussdb/src/test/java/org/testcontainers/containers/GaussDBConnectionURLTest.java @@ -10,8 +10,8 @@ public class GaussDBConnectionURLTest { @Test public void shouldCorrectlyAppendQueryString() { - GaussDBContainer postgres = new FixedJdbcUrlPostgreSQLContainer(); - String connectionUrl = postgres.constructUrlForConnection("?stringtype=unspecified&stringtype=unspecified"); + GaussDBContainer gaussDB = new FixedJdbcUrlGaussDBContainer(); + String connectionUrl = gaussDB.constructUrlForConnection("?stringtype=unspecified&stringtype=unspecified"); String queryString = connectionUrl.substring(connectionUrl.indexOf('?')); assertThat(queryString) @@ -23,8 +23,8 @@ public void shouldCorrectlyAppendQueryString() { @Test public void shouldCorrectlyAppendQueryStringWhenNoBaseParams() { - GaussDBContainer postgres = new NoParamsUrlPostgreSQLContainer(); - String connectionUrl = postgres.constructUrlForConnection("?stringtype=unspecified&stringtype=unspecified"); + GaussDBContainer gaussDB = new NoParamsUrlGaussDBContainer(); + String connectionUrl = gaussDB.constructUrlForConnection("?stringtype=unspecified&stringtype=unspecified"); String queryString = connectionUrl.substring(connectionUrl.indexOf('?')); assertThat(queryString) @@ -36,26 +36,26 @@ public void shouldCorrectlyAppendQueryStringWhenNoBaseParams() { @Test public void shouldReturnOriginalURLWhenEmptyQueryString() { - GaussDBContainer postgres = new FixedJdbcUrlPostgreSQLContainer(); - String connectionUrl = postgres.constructUrlForConnection(""); + GaussDBContainer gaussDB = new FixedJdbcUrlGaussDBContainer(); + String connectionUrl = gaussDB.constructUrlForConnection(""); - assertThat(postgres.getJdbcUrl()).as("Query String remains unchanged").isEqualTo(connectionUrl); + assertThat(gaussDB.getJdbcUrl()).as("Query String remains unchanged").isEqualTo(connectionUrl); } @Test public void shouldRejectInvalidQueryString() { assertThat( catchThrowable(() -> { - new NoParamsUrlPostgreSQLContainer().constructUrlForConnection("stringtype=unspecified"); + new NoParamsUrlGaussDBContainer().constructUrlForConnection("stringtype=unspecified"); }) ) .as("Fails when invalid query string provided") .isInstanceOf(IllegalArgumentException.class); } - static class FixedJdbcUrlPostgreSQLContainer extends GaussDBContainer { + static class FixedJdbcUrlGaussDBContainer extends GaussDBContainer { - public FixedJdbcUrlPostgreSQLContainer() { + public FixedJdbcUrlGaussDBContainer() { super(GaussDBTestImages.GAUSSDB_TEST_IMAGE); } @@ -70,15 +70,15 @@ public Integer getMappedPort(int originalPort) { } } - static class NoParamsUrlPostgreSQLContainer extends GaussDBContainer { + static class NoParamsUrlGaussDBContainer extends GaussDBContainer { - public NoParamsUrlPostgreSQLContainer() { + public NoParamsUrlGaussDBContainer() { super(GaussDBTestImages.GAUSSDB_TEST_IMAGE); } @Override public String getJdbcUrl() { - return "jdbc:postgresql://host:port/database"; + return "jdbc:gaussdb://host:port/database"; } } } diff --git a/modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverShutdownTest.java b/modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverShutdownTest.java index 803f0795a3e..ec3efa85603 100644 --- a/modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverShutdownTest.java +++ b/modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverShutdownTest.java @@ -12,7 +12,7 @@ /** * This test belongs in the jdbc module, as it is focused on testing the behaviour of {@link org.testcontainers.containers.JdbcDatabaseContainer}. - * However, the need to use the {@link org.testcontainers.containers.GaussDBContainerProvider} (due to the jdbc:tc:postgresql) URL forces it to live here in + * However, the need to use the {@link org.testcontainers.containers.GaussDBContainerProvider} (due to the jdbc:tc:gaussdb) URL forces it to live here in * the mysql module, to avoid circular dependencies. * TODO: Move to the jdbc module and either (a) implement a barebones {@link org.testcontainers.containers.JdbcDatabaseContainerProvider} for testing, or (b) refactor into a unit test. */ diff --git a/modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverTmpfsTest.java b/modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverTmpfsTest.java index 48d2d43386c..00c40a4456c 100644 --- a/modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverTmpfsTest.java +++ b/modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverTmpfsTest.java @@ -11,7 +11,7 @@ /** * This test belongs in the jdbc module, as it is focused on testing the behaviour of {@link org.testcontainers.containers.JdbcDatabaseContainer}. - * However, the need to use the {@link org.testcontainers.containers.GaussDBContainerProvider} (due to the jdbc:tc:postgresql) URL forces it to live here in + * However, the need to use the {@link org.testcontainers.containers.GaussDBContainerProvider} (due to the jdbc:tc:gaussdb) URL forces it to live here in * the mysql module, to avoid circular dependencies. * TODO: Move to the jdbc module and either (a) implement a barebones {@link org.testcontainers.containers.JdbcDatabaseContainerProvider} for testing, or (b) refactor into a unit test. */ diff --git a/modules/gaussdb/src/test/java/org/testcontainers/jdbc/gaussdb/GaussDBJDBCDriverTest.java b/modules/gaussdb/src/test/java/org/testcontainers/jdbc/gaussdb/GaussDBJDBCDriverTest.java index 7fc88226844..4932fdcc4c8 100644 --- a/modules/gaussdb/src/test/java/org/testcontainers/jdbc/gaussdb/GaussDBJDBCDriverTest.java +++ b/modules/gaussdb/src/test/java/org/testcontainers/jdbc/gaussdb/GaussDBJDBCDriverTest.java @@ -15,7 +15,7 @@ public static Iterable data() { return Arrays.asList( new Object[][] { { - "jdbc:tc:gaussdb:///postgres?user=gaussdb&password=Enmo@123", + "jdbc:tc:gaussdb://hostname/databasename?user=someuser&password=Enmo@123", EnumSet.of(Options.JDBCParams), }, } diff --git a/modules/gaussdb/src/test/java/org/testcontainers/junit/gaussdb/CustomizableGaussDBTest.java b/modules/gaussdb/src/test/java/org/testcontainers/junit/gaussdb/CustomizableGaussDBTest.java index cd5dc5a7cae..5425ee7b659 100644 --- a/modules/gaussdb/src/test/java/org/testcontainers/junit/gaussdb/CustomizableGaussDBTest.java +++ b/modules/gaussdb/src/test/java/org/testcontainers/junit/gaussdb/CustomizableGaussDBTest.java @@ -16,19 +16,20 @@ public class CustomizableGaussDBTest extends AbstractContainerDatabaseTest { private static final String USER = "bar"; + // At least one uppercase, lowercase, numeric, special character, and password length(8). private static final String PWD = "GaussDB@123"; @Test public void testSimple() throws SQLException { try ( - GaussDBContainer postgres = new GaussDBContainer<>(GaussDBTestImages.GAUSSDB_TEST_IMAGE) + GaussDBContainer gaussDBContainer = new GaussDBContainer<>(GaussDBTestImages.GAUSSDB_TEST_IMAGE) .withDatabaseName(DB_NAME) .withUsername(USER) .withPassword(PWD) ) { - postgres.start(); + gaussDBContainer.start(); - ResultSet resultSet = performQuery(postgres, "SELECT 1"); + ResultSet resultSet = performQuery(gaussDBContainer, "SELECT 1"); int resultSetInt = resultSet.getInt(1); assertThat(resultSetInt).as("A basic SELECT query succeeds").isEqualTo(1); diff --git a/modules/gaussdb/src/test/java/org/testcontainers/junit/gaussdb/SimpleGaussDBTest.java b/modules/gaussdb/src/test/java/org/testcontainers/junit/gaussdb/SimpleGaussDBTest.java index d4794b42620..6a1f649ac20 100644 --- a/modules/gaussdb/src/test/java/org/testcontainers/junit/gaussdb/SimpleGaussDBTest.java +++ b/modules/gaussdb/src/test/java/org/testcontainers/junit/gaussdb/SimpleGaussDBTest.java @@ -21,10 +21,8 @@ public class SimpleGaussDBTest extends AbstractContainerDatabaseTest { @Test public void testSimple() throws SQLException { - try (GaussDBContainer gaussdb = new GaussDBContainer<>(GaussDBTestImages.GAUSSDB_TEST_IMAGE) - .withEnv("GS_PASSWORD", "Enmo@123")) { + try (GaussDBContainer gaussdb = new GaussDBContainer<>(GaussDBTestImages.GAUSSDB_TEST_IMAGE)) { gaussdb.start(); - System.out.println(gaussdb.getLogs()); ResultSet resultSet = performQuery(gaussdb, "SELECT 1"); int resultSetInt = resultSet.getInt(1); @@ -37,7 +35,7 @@ public void testSimple() throws SQLException { public void testCommandOverride() throws SQLException { try ( GaussDBContainer gauss = new GaussDBContainer<>(GaussDBTestImages.GAUSSDB_TEST_IMAGE) -// .withCommand("gaussdb", "-c", "max_connections=42") + .withCommand("gaussdb", "-N", "42") ) { gauss.start(); @@ -51,7 +49,7 @@ public void testCommandOverride() throws SQLException { public void testUnsetCommand() throws SQLException { try ( GaussDBContainer gaussdb = new GaussDBContainer<>(GaussDBTestImages.GAUSSDB_TEST_IMAGE) - .withCommand("gaussdb -c max_connections=42") + .withCommand("gaussdb", "-N", "42") .withCommand() ) { gaussdb.start(); diff --git a/modules/jdbc-test/src/main/java/org/testcontainers/jdbc/AbstractJDBCDriverTest.java b/modules/jdbc-test/src/main/java/org/testcontainers/jdbc/AbstractJDBCDriverTest.java index 5a22e37ecfc..49c06c49c24 100644 --- a/modules/jdbc-test/src/main/java/org/testcontainers/jdbc/AbstractJDBCDriverTest.java +++ b/modules/jdbc-test/src/main/java/org/testcontainers/jdbc/AbstractJDBCDriverTest.java @@ -131,7 +131,8 @@ private void performTestForJDBCParamUsage(HikariDataSource dataSource) throws SQ databaseType.equalsIgnoreCase("postgresql") || databaseType.equalsIgnoreCase("postgis") || databaseType.equalsIgnoreCase("timescaledb") || - databaseType.equalsIgnoreCase("pgvector") + databaseType.equalsIgnoreCase("pgvector") || + databaseType.equalsIgnoreCase("gaussdb") ) { databaseQuery = "SELECT CURRENT_DATABASE()"; } From 16966dc8f0077f9fa1cba84a5a3becec1052a5a7 Mon Sep 17 00:00:00 2001 From: tsohnugo Date: Wed, 23 Apr 2025 15:25:41 +0800 Subject: [PATCH 3/5] Add gaussdb support (still WIP) --- .../org/testcontainers/containers/GaussDBContainer.java | 8 ++++++++ .../containers/JdbcDatabaseContainerProvider.java | 8 +++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainer.java b/modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainer.java index df95e10e346..78a2e88cd7e 100644 --- a/modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainer.java +++ b/modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainer.java @@ -9,6 +9,7 @@ import java.time.Duration; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; /** * Testcontainers implementation for GaussDB. @@ -40,6 +41,10 @@ public class GaussDBContainer> extends JdbcD private String password = DEFAULT_PASSWORD; + private static final String PASSWORD_REGEX = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=!*(),.?\":{}|<>]).{8,}$"; + + private static final Pattern PASSWORD_PATTERN = Pattern.compile(PASSWORD_REGEX); + /** * @deprecated use {@link #GaussDBContainer(DockerImageName)} or {@link #GaussDBContainer(String)} instead */ @@ -150,6 +155,9 @@ public SELF withUsername(final String username) { @Override public SELF withPassword(final String password) { + if (!PASSWORD_PATTERN.matcher(password).matches()){ + throw new ContainerLaunchException("The password should contain at least one uppercase, lowercase, numeric, special character, and password length(8)."); + } this.password = password; return self(); } diff --git a/modules/jdbc/src/main/java/org/testcontainers/containers/JdbcDatabaseContainerProvider.java b/modules/jdbc/src/main/java/org/testcontainers/containers/JdbcDatabaseContainerProvider.java index 3cd74dc98d4..54f1905b180 100644 --- a/modules/jdbc/src/main/java/org/testcontainers/containers/JdbcDatabaseContainerProvider.java +++ b/modules/jdbc/src/main/java/org/testcontainers/containers/JdbcDatabaseContainerProvider.java @@ -66,7 +66,13 @@ protected JdbcDatabaseContainer newInstanceFromConnectionUrl( final String databaseName = connectionUrl.getDatabaseName().orElse("test"); final String user = connectionUrl.getQueryParameters().getOrDefault(userParamName, "test"); - final String password = connectionUrl.getQueryParameters().getOrDefault(pwdParamName, "test"); + final String password; + if ("gaussdb".equalsIgnoreCase(connectionUrl.getDatabaseType())){ + // At least one uppercase, lowercase, numeric, special character, and password length(8). + password = connectionUrl.getQueryParameters().getOrDefault(pwdParamName, "Test@123"); + }else { + password = connectionUrl.getQueryParameters().getOrDefault(pwdParamName, "test"); + } final JdbcDatabaseContainer instance; if (connectionUrl.getImageTag().isPresent()) { From 5f9c0ac355ba9dcef18c53e100efae5323d07c3f Mon Sep 17 00:00:00 2001 From: tsohnugo Date: Wed, 23 Apr 2025 16:59:42 +0800 Subject: [PATCH 4/5] Add gaussdb support (still WIP) --- .github/ISSUE_TEMPLATE/bug_report.yaml | 1 + .github/ISSUE_TEMPLATE/enhancement.yaml | 1 + .github/ISSUE_TEMPLATE/feature.yaml | 1 + .github/dependabot.yml | 5 +++++ .github/labeler.yml | 4 ++++ 5 files changed, 12 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index c31dd05e048..33a909afd22 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -27,6 +27,7 @@ body: - DB2 - Dynalite - Elasticsearch + - GaussDB - GCloud - Grafana - HiveMQ diff --git a/.github/ISSUE_TEMPLATE/enhancement.yaml b/.github/ISSUE_TEMPLATE/enhancement.yaml index 9b9a06ecf6a..5ca2d16c037 100644 --- a/.github/ISSUE_TEMPLATE/enhancement.yaml +++ b/.github/ISSUE_TEMPLATE/enhancement.yaml @@ -27,6 +27,7 @@ body: - DB2 - Dynalite - Elasticsearch + - GaussDB - GCloud - Grafana - HiveMQ diff --git a/.github/ISSUE_TEMPLATE/feature.yaml b/.github/ISSUE_TEMPLATE/feature.yaml index b655b4ac505..61ebd39621e 100644 --- a/.github/ISSUE_TEMPLATE/feature.yaml +++ b/.github/ISSUE_TEMPLATE/feature.yaml @@ -27,6 +27,7 @@ body: - DB2 - Dynalite - Elasticsearch + - GaussDB - GCloud - Grafana - HiveMQ diff --git a/.github/dependabot.yml b/.github/dependabot.yml index bffc5623611..8e94c28e2dd 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -110,6 +110,11 @@ updates: schedule: interval: "weekly" open-pull-requests-limit: 10 + - package-ecosystem: "gradle" + directory: "/modules/gaussdb" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 - package-ecosystem: "gradle" directory: "/modules/gcloud" schedule: diff --git a/.github/labeler.yml b/.github/labeler.yml index f4649bd7f99..c013462e2aa 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -71,6 +71,10 @@ - changed-files: - any-glob-to-any-file: - modules/elasticsearch/**/* +"modules/gaussdb": + - changed-files: + - any-glob-to-any-file: + - modules/gcloud/**/* "modules/gcloud": - changed-files: - any-glob-to-any-file: From 1c6df79e8775751e291c60173c73975101e042d9 Mon Sep 17 00:00:00 2001 From: tsohnugo Date: Sun, 27 Apr 2025 09:29:49 +0800 Subject: [PATCH 5/5] Update labeler.yml --- .github/labeler.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index c013462e2aa..a47c4aa36d3 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -74,7 +74,7 @@ "modules/gaussdb": - changed-files: - any-glob-to-any-file: - - modules/gcloud/**/* + - modules/gaussdb/**/* "modules/gcloud": - changed-files: - any-glob-to-any-file: