Skip to content

Commit 591b19b

Browse files
committed
GaussDB is adapted to testcontainers
1 parent 55f2347 commit 591b19b

File tree

14 files changed

+600
-0
lines changed

14 files changed

+600
-0
lines changed

modules/gaussdb/build.gradle

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
description = "Testcontainers :: JDBC :: GaussDB"
2+
3+
dependencies {
4+
api project(':jdbc')
5+
6+
compileOnly project(':r2dbc')
7+
// compileOnly 'io.r2dbc:r2dbc-postgresql:0.8.13.RELEASE'
8+
9+
testImplementation project(':jdbc-test')
10+
testRuntimeOnly 'com.huaweicloud.gaussdb:gaussdbjdbc:506.0.0.b058'
11+
12+
// testImplementation testFixtures(project(':r2dbc'))
13+
// testRuntimeOnly 'io.r2dbc:r2dbc-postgresql:0.8.13.RELEASE'
14+
15+
compileOnly 'org.jetbrains:annotations:24.1.0'
16+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package org.testcontainers.containers;
2+
3+
import org.jetbrains.annotations.NotNull;
4+
import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;
5+
import org.testcontainers.utility.DockerImageName;
6+
7+
import java.time.Duration;
8+
import java.time.temporal.ChronoUnit;
9+
import java.util.Set;
10+
11+
/**
12+
* Testcontainers implementation for PostgreSQL.
13+
* <p>
14+
* Supported images: {@code postgres}, {@code pgvector/pgvector}
15+
* <p>
16+
* Exposed ports: 5432
17+
*/
18+
public class GaussDBContainer<SELF extends GaussDBContainer<SELF>> extends JdbcDatabaseContainer<SELF> {
19+
20+
public static final String NAME = "gaussdb";
21+
22+
public static final String IMAGE = "opengauss/opengauss";
23+
24+
public static final String DEFAULT_TAG = "latest";
25+
26+
private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("opengauss/opengauss").asCompatibleSubstituteFor("gaussdb");
27+
28+
public static final Integer GaussDB_PORT = 5432;
29+
30+
static final String DEFAULT_USER = "gaussdb";
31+
32+
static final String DEFAULT_PASSWORD = "Enmo@123";
33+
34+
private String databaseName = "postgres";
35+
36+
private String username = "gaussdb";
37+
38+
private String password = "Enmo@123";
39+
40+
private static final String FSYNC_OFF_OPTION = "fsync=off";
41+
42+
/**
43+
* @deprecated use {@link #GaussDBContainer(DockerImageName)} or {@link #GaussDBContainer(String)} instead
44+
*/
45+
@Deprecated
46+
public GaussDBContainer() {
47+
this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG));
48+
}
49+
50+
public GaussDBContainer(final String dockerImageName) {
51+
this(DockerImageName.parse(dockerImageName));
52+
}
53+
54+
public GaussDBContainer(final DockerImageName dockerImageName) {
55+
super(dockerImageName);
56+
dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);
57+
this.withEnv("GS_PASSWORD", "Enmo@123")
58+
.withDatabaseName("postgres");
59+
// Comment out the test error code for the time being
60+
// this.waitStrategy
61+
// =
62+
// new LogMessageWaitStrategy()
63+
// .withRegEx(".*can not read GAUSS_WARNING_TYPE env.*\\s")
64+
// .withTimes(1)
65+
// .withStartupTimeout(Duration.of(60, ChronoUnit.SECONDS));
66+
// this.setCommand("-c", FSYNC_OFF_OPTION);
67+
68+
addExposedPort(GaussDB_PORT);
69+
}
70+
71+
/**
72+
* @return the ports on which to check if the container is ready
73+
* @deprecated use {@link #getLivenessCheckPortNumbers()} instead
74+
*/
75+
@NotNull
76+
@Override
77+
@Deprecated
78+
protected Set<Integer> getLivenessCheckPorts() {
79+
return super.getLivenessCheckPorts();
80+
}
81+
82+
@Override
83+
protected void configure() {
84+
// Disable Postgres driver use of java.util.logging to reduce noise at startup time
85+
withUrlParam("loggerLevel", "OFF");
86+
addEnv("POSTGRES_DB", databaseName);
87+
addEnv("POSTGRES_USER", username);
88+
addEnv("POSTGRES_PASSWORD", password);
89+
}
90+
91+
@Override
92+
public String getDriverClassName() {
93+
return "com.huawei.gaussdb.jdbc.Driver";
94+
}
95+
96+
@Override
97+
public String getJdbcUrl() {
98+
String additionalUrlParams = constructUrlParameters("?", "&");
99+
return (
100+
"jdbc:gaussdb://" +
101+
getHost() +
102+
":" +
103+
getMappedPort(GaussDB_PORT) +
104+
"/" +
105+
databaseName +
106+
additionalUrlParams
107+
);
108+
}
109+
110+
@Override
111+
public String getDatabaseName() {
112+
return databaseName;
113+
}
114+
115+
@Override
116+
public String getUsername() {
117+
return username;
118+
}
119+
120+
@Override
121+
public String getPassword() {
122+
return password;
123+
}
124+
125+
@Override
126+
public String getTestQueryString() {
127+
return "SELECT 1";
128+
}
129+
130+
@Override
131+
public SELF withDatabaseName(final String databaseName) {
132+
this.databaseName = databaseName;
133+
return self();
134+
}
135+
136+
@Override
137+
public SELF withUsername(final String username) {
138+
this.username = username;
139+
return self();
140+
}
141+
142+
@Override
143+
public SELF withPassword(final String password) {
144+
this.password = password;
145+
return self();
146+
}
147+
148+
@Override
149+
protected void waitUntilContainerStarted() {
150+
getWaitStrategy().waitUntilReady(this);
151+
}
152+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package org.testcontainers.containers;
2+
3+
import org.testcontainers.jdbc.ConnectionUrl;
4+
import org.testcontainers.utility.DockerImageName;
5+
6+
/**
7+
* Factory for GaussDB containers.
8+
*/
9+
public class GaussDBContainerProvider extends JdbcDatabaseContainerProvider {
10+
11+
public static final String USER_PARAM = "user";
12+
13+
public static final String PASSWORD_PARAM = "password";
14+
15+
@Override
16+
public boolean supports(String databaseType) {
17+
return databaseType.equals(GaussDBContainer.NAME);
18+
}
19+
20+
@Override
21+
public JdbcDatabaseContainer newInstance() {
22+
return newInstance(GaussDBContainer.DEFAULT_TAG);
23+
}
24+
25+
@Override
26+
public JdbcDatabaseContainer newInstance(String tag) {
27+
return new GaussDBContainer(DockerImageName.parse(GaussDBContainer.IMAGE).withTag(tag));
28+
}
29+
30+
@Override
31+
public JdbcDatabaseContainer newInstance(ConnectionUrl connectionUrl) {
32+
return newInstanceFromConnectionUrl(connectionUrl, USER_PARAM, PASSWORD_PARAM);
33+
}
34+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org.testcontainers.containers.GaussDBContainerProvider
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.testcontainers;
2+
3+
import org.testcontainers.utility.DockerImageName;
4+
5+
public interface GaussDBTestImages {
6+
DockerImageName GAUSSDB_TEST_IMAGE = DockerImageName.parse("opengauss/opengauss:latest").asCompatibleSubstituteFor("gaussdb");
7+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package org.testcontainers.containers;
2+
3+
import org.junit.Test;
4+
import org.testcontainers.GaussDBTestImages;
5+
6+
import static org.assertj.core.api.Assertions.assertThat;
7+
import static org.assertj.core.api.Assertions.catchThrowable;
8+
9+
public class GaussDBConnectionURLTest {
10+
11+
@Test
12+
public void shouldCorrectlyAppendQueryString() {
13+
GaussDBContainer<?> postgres = new FixedJdbcUrlPostgreSQLContainer();
14+
String connectionUrl = postgres.constructUrlForConnection("?stringtype=unspecified&stringtype=unspecified");
15+
String queryString = connectionUrl.substring(connectionUrl.indexOf('?'));
16+
17+
assertThat(queryString)
18+
.as("Query String contains expected params")
19+
.contains("?stringtype=unspecified&stringtype=unspecified");
20+
assertThat(queryString.indexOf('?')).as("Query String starts with '?'").isZero();
21+
assertThat(queryString.substring(1)).as("Query String does not contain extra '?'").doesNotContain("?");
22+
}
23+
24+
@Test
25+
public void shouldCorrectlyAppendQueryStringWhenNoBaseParams() {
26+
GaussDBContainer<?> postgres = new NoParamsUrlPostgreSQLContainer();
27+
String connectionUrl = postgres.constructUrlForConnection("?stringtype=unspecified&stringtype=unspecified");
28+
String queryString = connectionUrl.substring(connectionUrl.indexOf('?'));
29+
30+
assertThat(queryString)
31+
.as("Query String contains expected params")
32+
.contains("?stringtype=unspecified&stringtype=unspecified");
33+
assertThat(queryString.indexOf('?')).as("Query String starts with '?'").isZero();
34+
assertThat(queryString.substring(1)).as("Query String does not contain extra '?'").doesNotContain("?");
35+
}
36+
37+
@Test
38+
public void shouldReturnOriginalURLWhenEmptyQueryString() {
39+
GaussDBContainer<?> postgres = new FixedJdbcUrlPostgreSQLContainer();
40+
String connectionUrl = postgres.constructUrlForConnection("");
41+
42+
assertThat(postgres.getJdbcUrl()).as("Query String remains unchanged").isEqualTo(connectionUrl);
43+
}
44+
45+
@Test
46+
public void shouldRejectInvalidQueryString() {
47+
assertThat(
48+
catchThrowable(() -> {
49+
new NoParamsUrlPostgreSQLContainer().constructUrlForConnection("stringtype=unspecified");
50+
})
51+
)
52+
.as("Fails when invalid query string provided")
53+
.isInstanceOf(IllegalArgumentException.class);
54+
}
55+
56+
static class FixedJdbcUrlPostgreSQLContainer extends GaussDBContainer<FixedJdbcUrlPostgreSQLContainer> {
57+
58+
public FixedJdbcUrlPostgreSQLContainer() {
59+
super(GaussDBTestImages.GAUSSDB_TEST_IMAGE);
60+
}
61+
62+
@Override
63+
public String getHost() {
64+
return "localhost";
65+
}
66+
67+
@Override
68+
public Integer getMappedPort(int originalPort) {
69+
return 34532;
70+
}
71+
}
72+
73+
static class NoParamsUrlPostgreSQLContainer extends GaussDBContainer<FixedJdbcUrlPostgreSQLContainer> {
74+
75+
public NoParamsUrlPostgreSQLContainer() {
76+
super(GaussDBTestImages.GAUSSDB_TEST_IMAGE);
77+
}
78+
79+
@Override
80+
public String getJdbcUrl() {
81+
return "jdbc:postgresql://host:port/database";
82+
}
83+
}
84+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package org.testcontainers.jdbc;
2+
3+
import org.junit.AfterClass;
4+
import org.junit.Test;
5+
import org.testcontainers.containers.JdbcDatabaseContainer;
6+
7+
import java.sql.Connection;
8+
import java.sql.DriverManager;
9+
import java.sql.SQLException;
10+
11+
import static org.assertj.core.api.Assertions.assertThat;
12+
13+
/**
14+
* This test belongs in the jdbc module, as it is focused on testing the behaviour of {@link org.testcontainers.containers.JdbcDatabaseContainer}.
15+
* However, the need to use the {@link org.testcontainers.containers.GaussDBContainerProvider} (due to the jdbc:tc:postgresql) URL forces it to live here in
16+
* the mysql module, to avoid circular dependencies.
17+
* 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.
18+
*/
19+
public class DatabaseDriverShutdownTest {
20+
21+
@AfterClass
22+
public static void testCleanup() {
23+
ContainerDatabaseDriver.killContainers();
24+
}
25+
26+
@Test
27+
public void shouldStopContainerWhenAllConnectionsClosed() throws SQLException {
28+
final String jdbcUrl = "jdbc:tc:gaussdb://hostname/databasename";
29+
30+
getConnectionAndClose(jdbcUrl);
31+
32+
JdbcDatabaseContainer<?> container = ContainerDatabaseDriver.getContainer(jdbcUrl);
33+
assertThat(container).as("Database container instance is null as expected").isNull();
34+
}
35+
36+
@Test
37+
public void shouldNotStopDaemonContainerWhenAllConnectionsClosed() throws SQLException {
38+
final String jdbcUrl = "jdbc:tc:gaussdb://hostname/databasename?TC_DAEMON=true";
39+
40+
getConnectionAndClose(jdbcUrl);
41+
42+
JdbcDatabaseContainer<?> container = ContainerDatabaseDriver.getContainer(jdbcUrl);
43+
assertThat(container).as("Database container instance is not null as expected").isNotNull();
44+
assertThat(container.isRunning()).as("Database container is running as expected").isTrue();
45+
}
46+
47+
private void getConnectionAndClose(String jdbcUrl) throws SQLException {
48+
try (Connection connection = DriverManager.getConnection(jdbcUrl)) {
49+
assertThat(connection).as("Obtained connection as expected").isNotNull();
50+
}
51+
}
52+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package org.testcontainers.jdbc;
2+
3+
import org.junit.Test;
4+
import org.testcontainers.containers.Container;
5+
import org.testcontainers.containers.JdbcDatabaseContainer;
6+
7+
import java.sql.Connection;
8+
import java.sql.DriverManager;
9+
10+
import static org.assertj.core.api.Assertions.assertThat;
11+
12+
/**
13+
* This test belongs in the jdbc module, as it is focused on testing the behaviour of {@link org.testcontainers.containers.JdbcDatabaseContainer}.
14+
* However, the need to use the {@link org.testcontainers.containers.GaussDBContainerProvider} (due to the jdbc:tc:postgresql) URL forces it to live here in
15+
* the mysql module, to avoid circular dependencies.
16+
* 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.
17+
*/
18+
public class DatabaseDriverTmpfsTest {
19+
20+
@Test
21+
public void testDatabaseHasTmpFsViaConnectionString() throws Exception {
22+
final String jdbcUrl = "jdbc:tc:gaussdb://hostname/databasename?TC_TMPFS=/testtmpfs:rw";
23+
try (Connection ignored = DriverManager.getConnection(jdbcUrl)) {
24+
JdbcDatabaseContainer<?> container = ContainerDatabaseDriver.getContainer(jdbcUrl);
25+
// check file doesn't exist
26+
String path = "/testtmpfs/test.file";
27+
Container.ExecResult execResult = container.execInContainer("ls", path);
28+
assertThat(execResult.getExitCode())
29+
.as("tmpfs inside container doesn't have file that doesn't exist")
30+
.isNotZero();
31+
// touch && check file does exist
32+
container.execInContainer("touch", path);
33+
execResult = container.execInContainer("ls", path);
34+
assertThat(execResult.getExitCode()).as("tmpfs inside container has file that does exist").isZero();
35+
}
36+
}
37+
}

0 commit comments

Comments
 (0)