From b68d9997f9e499e075b0a2c31e94b663acb6be2e Mon Sep 17 00:00:00 2001 From: Miguel Gonzalez Date: Sat, 27 Aug 2016 23:05:52 +0200 Subject: [PATCH] Implement MariaDB container --- AUTHORS | 3 +- modules/mariadb/pom.xml | 59 +++++++ .../containers/MariaDBContainer.java | 73 ++++++++ .../containers/MariaDBContainerProvider.java | 16 ++ ...s.containers.JdbcDatabaseContainerProvider | 1 + .../testcontainers/jdbc/JDBCDriverTest.java | 133 ++++++++++++++ .../jdbc/JDBCDriverWithPoolTest.java | 165 ++++++++++++++++++ .../junit/SimpleMariaDBTest.java | 87 +++++++++ .../src/test/resources/logback-test.xml | 29 +++ .../test/resources/somepath/init_mariadb.sql | 5 + .../somepath/mariadb_conf_override/my.cnf | 2 + pom.xml | 6 + 12 files changed, 578 insertions(+), 1 deletion(-) create mode 100644 modules/mariadb/pom.xml create mode 100644 modules/mariadb/src/main/java/org/testcontainers/containers/MariaDBContainer.java create mode 100644 modules/mariadb/src/main/java/org/testcontainers/containers/MariaDBContainerProvider.java create mode 100644 modules/mariadb/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider create mode 100644 modules/mariadb/src/test/java/org/testcontainers/jdbc/JDBCDriverTest.java create mode 100644 modules/mariadb/src/test/java/org/testcontainers/jdbc/JDBCDriverWithPoolTest.java create mode 100644 modules/mariadb/src/test/java/org/testcontainers/junit/SimpleMariaDBTest.java create mode 100644 modules/mariadb/src/test/resources/logback-test.xml create mode 100644 modules/mariadb/src/test/resources/somepath/init_mariadb.sql create mode 100644 modules/mariadb/src/test/resources/somepath/mariadb_conf_override/my.cnf diff --git a/AUTHORS b/AUTHORS index 9740f417747..f67f444bf02 100644 --- a/AUTHORS +++ b/AUTHORS @@ -7,4 +7,5 @@ Krystian Nowak Viktor Schulz Asaf Mesika Sergei Egorov -Pete Cornish \ No newline at end of file +Pete Cornish +Miguel Gonzalez Sanchez diff --git a/modules/mariadb/pom.xml b/modules/mariadb/pom.xml new file mode 100644 index 00000000000..5392b0f7f76 --- /dev/null +++ b/modules/mariadb/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + + org.testcontainers + testcontainers-parent + 1.1.6-SNAPSHOT + ../../pom.xml + + + mariadb + TestContainers :: JDBC :: MariaDB + + + + ${project.groupId} + testcontainers + ${project.version} + + + ${project.groupId} + jdbc + ${project.version} + + + + + org.mariadb.jdbc + mariadb-java-client + 1.4.6 + test + + + + + com.zaxxer + HikariCP-java6 + 2.3.8 + test + + + commons-dbutils + commons-dbutils + 1.6 + test + + + org.apache.tomcat + tomcat-jdbc + 8.5.4 + + + org.vibur + vibur-dbcp + 9.0 + + + diff --git a/modules/mariadb/src/main/java/org/testcontainers/containers/MariaDBContainer.java b/modules/mariadb/src/main/java/org/testcontainers/containers/MariaDBContainer.java new file mode 100644 index 00000000000..4b5ba4187fa --- /dev/null +++ b/modules/mariadb/src/main/java/org/testcontainers/containers/MariaDBContainer.java @@ -0,0 +1,73 @@ +package org.testcontainers.containers; + +/** + * Container implementation for the MariaDB project. + * + * @author Miguel Gonzalez Sanchez + */ +public class MariaDBContainer> extends JdbcDatabaseContainer { + + public static final String NAME = "mariadb"; + public static final String IMAGE = "mariadb"; + private static final Integer MARIADB_PORT = 3306; + private static final String MARIADB_USER = "test"; + private static final String MARIADB_PASSWORD = "test"; + private static final String MARIADB_DATABASE = "test"; + private static final String MY_CNF_CONFIG_OVERRIDE_PARAM_NAME = "TC_MY_CNF"; + + public MariaDBContainer() { + super(IMAGE + ":latest"); + } + + public MariaDBContainer(String dockerImageName) { + super(dockerImageName); + } + + @Override + protected Integer getLivenessCheckPort() { + return getMappedPort(MARIADB_PORT); + } + + @Override + protected void configure() { + optionallyMapResourceParameterAsVolume(MY_CNF_CONFIG_OVERRIDE_PARAM_NAME, "/etc/mysql/conf.d"); + + addExposedPort(MARIADB_PORT); + addEnv("MYSQL_DATABASE", MARIADB_DATABASE); + addEnv("MYSQL_USER", MARIADB_USER); + addEnv("MYSQL_PASSWORD", MARIADB_PASSWORD); + addEnv("MYSQL_ROOT_PASSWORD", MARIADB_PASSWORD); + setCommand("mysqld"); + setStartupAttempts(3); + } + + @Override + public String getDriverClassName() { + return "org.mariadb.jdbc.Driver"; + } + + @Override + public String getJdbcUrl() { + return "jdbc:mariadb://" + getContainerIpAddress() + ":" + getMappedPort(MARIADB_PORT) + "/test"; + } + + @Override + public String getUsername() { + return MARIADB_USER; + } + + @Override + public String getPassword() { + return MARIADB_PASSWORD; + } + + @Override + public String getTestQueryString() { + return "SELECT 1"; + } + + public SELF withConfigurationOverride(String s) { + parameters.put(MY_CNF_CONFIG_OVERRIDE_PARAM_NAME, s); + return self(); + } +} diff --git a/modules/mariadb/src/main/java/org/testcontainers/containers/MariaDBContainerProvider.java b/modules/mariadb/src/main/java/org/testcontainers/containers/MariaDBContainerProvider.java new file mode 100644 index 00000000000..b3171b02911 --- /dev/null +++ b/modules/mariadb/src/main/java/org/testcontainers/containers/MariaDBContainerProvider.java @@ -0,0 +1,16 @@ +package org.testcontainers.containers; + +/** + * Factory for MariaDB containers. + */ +public class MariaDBContainerProvider extends JdbcDatabaseContainerProvider { + @Override + public boolean supports(String databaseType) { + return databaseType.equals(MariaDBContainer.NAME); + } + + @Override + public JdbcDatabaseContainer newInstance(String tag) { + return new MariaDBContainer(MariaDBContainer.IMAGE + ":" + tag); + } +} diff --git a/modules/mariadb/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider b/modules/mariadb/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider new file mode 100644 index 00000000000..f5b51466d19 --- /dev/null +++ b/modules/mariadb/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider @@ -0,0 +1 @@ +org.testcontainers.containers.MariaDBContainerProvider \ No newline at end of file diff --git a/modules/mariadb/src/test/java/org/testcontainers/jdbc/JDBCDriverTest.java b/modules/mariadb/src/test/java/org/testcontainers/jdbc/JDBCDriverTest.java new file mode 100644 index 00000000000..f087d384e16 --- /dev/null +++ b/modules/mariadb/src/test/java/org/testcontainers/jdbc/JDBCDriverTest.java @@ -0,0 +1,133 @@ +package org.testcontainers.jdbc; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import org.apache.commons.dbutils.QueryRunner; +import org.apache.commons.dbutils.ResultSetHandler; +import org.apache.commons.lang.SystemUtils; +import org.junit.After; +import org.junit.Test; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import static org.junit.Assume.assumeFalse; +import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals; + +/** + * + */ +public class JDBCDriverTest { + + @After + public void testCleanup() { + ContainerDatabaseDriver.killContainers(); + } + + @Test + public void testMariaDBWithVersion() throws SQLException { + performSimpleTest("jdbc:tc:mariadb:10.1.16://hostname/databasename"); + } + + @Test + public void testMariaDBWithNoSpecifiedVersion() throws SQLException { + performSimpleTest("jdbc:tc:mariadb://hostname/databasename"); + } + + @Test + public void testMariaDBWithCustomIniFile() throws SQLException { + assumeFalse(SystemUtils.IS_OS_WINDOWS); + HikariDataSource ds = getDataSource("jdbc:tc:mariadb:10.1.16://hostname/databasename?TC_MY_CNF=somepath/mariadb_conf_override", 1); + Statement statement = ds.getConnection().createStatement(); + statement.execute("SELECT @@GLOBAL.innodb_file_format"); + ResultSet resultSet = statement.getResultSet(); + + resultSet.next(); + String result = resultSet.getString(1); + + assertEquals("The InnoDB file format has been set by the ini file content", "Barracuda", result); + } + + @Test + public void testMariaDBWithClasspathInitScript() throws SQLException { + performSimpleTest("jdbc:tc:mariadb://hostname/databasename?TC_INITSCRIPT=somepath/init_mariadb.sql"); + + performTestForScriptedSchema("jdbc:tc:mariadb://hostname/databasename?TC_INITSCRIPT=somepath/init_mariadb.sql"); + } + + @Test + public void testMariaDBWithClasspathInitFunction() throws SQLException { + performSimpleTest("jdbc:tc:mariadb://hostname/databasename?TC_INITFUNCTION=org.testcontainers.jdbc.JDBCDriverTest::sampleInitFunction"); + + performTestForScriptedSchema("jdbc:tc:mariadb://hostname/databasename?TC_INITFUNCTION=org.testcontainers.jdbc.JDBCDriverTest::sampleInitFunction"); + } + + @Test + public void testMariaDBWithQueryParams() throws SQLException { + performSimpleTestWithCharacterSet("jdbc:tc:mariadb://hostname/databasename?useUnicode=yes&characterEncoding=utf8"); + } + + private void performSimpleTest(String jdbcUrl) throws SQLException { + HikariDataSource dataSource = getDataSource(jdbcUrl, 1); + new QueryRunner(dataSource).query("SELECT 1", new ResultSetHandler() { + @Override + public Object handle(ResultSet rs) throws SQLException { + rs.next(); + int resultSetInt = rs.getInt(1); + assertEquals("A basic SELECT query succeeds", 1, resultSetInt); + return true; + } + }); + dataSource.close(); + } + + private void performTestForScriptedSchema(String jdbcUrl) throws SQLException { + HikariDataSource dataSource = getDataSource(jdbcUrl, 1); + new QueryRunner(dataSource).query("SELECT foo FROM bar WHERE foo LIKE '%world'", new ResultSetHandler() { + @Override + public Object handle(ResultSet rs) throws SQLException { + rs.next(); + String resultSetString = rs.getString(1); + assertEquals("A basic SELECT query succeeds where the schema has been applied from a script", "hello world", resultSetString); + return true; + } + }); + dataSource.close(); + } + + private void performSimpleTestWithCharacterSet(String jdbcUrl) throws SQLException { + HikariDataSource dataSource = getDataSource(jdbcUrl, 1); + new QueryRunner(dataSource).query("SHOW VARIABLES LIKE 'character\\_set\\_connection'", new ResultSetHandler() { + @Override + public Object handle(ResultSet rs) throws SQLException { + rs.next(); + String resultSetInt = rs.getString(2); + assertEquals("Passing query parameters to set DB connection encoding is successful", "utf8", resultSetInt); + return true; + } + }); + dataSource.close(); + } + + private HikariDataSource getDataSource(String jdbcUrl, int poolSize) { + HikariConfig hikariConfig = new HikariConfig(); + hikariConfig.setJdbcUrl(jdbcUrl); + hikariConfig.setConnectionTestQuery("SELECT 1"); + hikariConfig.setMinimumIdle(1); + hikariConfig.setMaximumPoolSize(poolSize); + + return new HikariDataSource(hikariConfig); + } + + public static void sampleInitFunction(Connection connection) throws SQLException { + connection.createStatement().execute("CREATE TABLE bar (\n" + + " foo VARCHAR(255)\n" + + ");"); + connection.createStatement().execute("INSERT INTO bar (foo) VALUES ('hello world');"); + connection.createStatement().execute("CREATE TABLE my_counter (\n" + + " n INT\n" + + ");"); + } +} diff --git a/modules/mariadb/src/test/java/org/testcontainers/jdbc/JDBCDriverWithPoolTest.java b/modules/mariadb/src/test/java/org/testcontainers/jdbc/JDBCDriverWithPoolTest.java new file mode 100644 index 00000000000..590e83c8a95 --- /dev/null +++ b/modules/mariadb/src/test/java/org/testcontainers/jdbc/JDBCDriverWithPoolTest.java @@ -0,0 +1,165 @@ +package org.testcontainers.jdbc; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import org.apache.commons.dbutils.QueryRunner; +import org.apache.commons.dbutils.ResultSetHandler; +import org.apache.tomcat.jdbc.pool.PoolProperties; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.vibur.dbcp.ViburDBCPDataSource; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +import static java.util.Arrays.asList; +import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals; + +/** + * + */ +@RunWith(Parameterized.class) +public class JDBCDriverWithPoolTest { + + public static final String URL = "jdbc:tc:mariadb://hostname/databasename?TC_INITFUNCTION=org.testcontainers.jdbc.JDBCDriverWithPoolTest::sampleInitFunction"; + private final DataSource dataSource; + + @Parameterized.Parameters + public static Iterable> dataSourceSuppliers() { + return asList( + JDBCDriverWithPoolTest::getTomcatDataSourceWithDriverClassName, + JDBCDriverWithPoolTest::getTomcatDataSource, + JDBCDriverWithPoolTest::getHikariDataSourceWithDriverClassName, + JDBCDriverWithPoolTest::getHikariDataSource, + JDBCDriverWithPoolTest::getViburDataSourceWithDriverClassName, + JDBCDriverWithPoolTest::getViburDataSource + ); + } + + public JDBCDriverWithPoolTest(Supplier dataSourceSupplier) { + this.dataSource = dataSourceSupplier.get(); + } + + private ExecutorService executorService = Executors.newFixedThreadPool(5); + + @Test + public void testMariaDBWithConnectionPoolUsingSameContainer() throws SQLException, InterruptedException { + + // Populate the database with some data in multiple threads, so that multiple connections from the pool will be used + for (int i = 0; i < 100; i++) { + executorService.submit(() -> { + try { + new QueryRunner(dataSource).insert("INSERT INTO my_counter (n) VALUES (5)", + (ResultSetHandler) rs -> true); + } catch (SQLException e) { + e.printStackTrace(); + } + }); + } + + // Complete population of the database + executorService.shutdown(); + executorService.awaitTermination(5, TimeUnit.MINUTES); + + // compare to expected results + int count = new QueryRunner(dataSource).query("SELECT COUNT(1) FROM my_counter", rs -> { + rs.next(); + return rs.getInt(1); + }); + assertEquals("Reuse of a datasource points to the same DB container", 100, count); + + + int sum = new QueryRunner(dataSource).query("SELECT SUM(n) FROM my_counter", rs -> { + rs.next(); + return rs.getInt(1); + }); + // 100 records * 5 = 500 expected + assertEquals("Reuse of a datasource points to the same DB container", 500, sum); + } + + + private static DataSource getTomcatDataSourceWithDriverClassName() { + PoolProperties poolProperties = new PoolProperties(); + poolProperties.setUrl(URL + ";TEST=TOMCAT_WITH_CLASSNAME"); // append a dummy URL element to ensure different DB per test + poolProperties.setValidationQuery("SELECT 1"); + poolProperties.setMinIdle(3); + poolProperties.setMaxActive(10); + poolProperties.setDriverClassName(ContainerDatabaseDriver.class.getName()); + + return new org.apache.tomcat.jdbc.pool.DataSource(poolProperties); + } + + private static DataSource getTomcatDataSource() { + PoolProperties poolProperties = new PoolProperties(); + poolProperties.setUrl(URL + ";TEST=TOMCAT"); // append a dummy URL element to ensure different DB per test + poolProperties.setValidationQuery("SELECT 1"); + poolProperties.setInitialSize(3); + poolProperties.setMaxActive(10); + + return new org.apache.tomcat.jdbc.pool.DataSource(poolProperties); + } + + private static HikariDataSource getHikariDataSourceWithDriverClassName() { + HikariConfig hikariConfig = new HikariConfig(); + hikariConfig.setJdbcUrl(URL + ";TEST=HIKARI_WITH_CLASSNAME"); // append a dummy URL element to ensure different DB per test + hikariConfig.setConnectionTestQuery("SELECT 1"); + hikariConfig.setMinimumIdle(3); + hikariConfig.setMaximumPoolSize(10); + hikariConfig.setDriverClassName(ContainerDatabaseDriver.class.getName()); + + return new HikariDataSource(hikariConfig); + } + + private static HikariDataSource getHikariDataSource() { + HikariConfig hikariConfig = new HikariConfig(); + hikariConfig.setJdbcUrl(URL + ";TEST=HIKARI"); // append a dummy URL element to ensure different DB per test + hikariConfig.setConnectionTestQuery("SELECT 1"); + hikariConfig.setMinimumIdle(3); + hikariConfig.setMaximumPoolSize(10); + + return new HikariDataSource(hikariConfig); + } + + private static DataSource getViburDataSourceWithDriverClassName() { + ViburDBCPDataSource ds = new ViburDBCPDataSource(); + + ds.setJdbcUrl(URL + ";TEST=VIBUR_WITH_CLASSNAME"); + ds.setPoolInitialSize(3); + ds.setPoolMaxSize(10); + ds.setTestConnectionQuery("SELECT 1"); + ds.setDriverClassName(ContainerDatabaseDriver.class.getName()); + + ds.start(); + + return ds; + } + + private static DataSource getViburDataSource() { + ViburDBCPDataSource ds = new ViburDBCPDataSource(); + ds.setJdbcUrl(URL + ";TEST=VIBUR"); + ds.setPoolInitialSize(3); + ds.setPoolMaxSize(10); + ds.setTestConnectionQuery("SELECT 1"); + + ds.start(); + + return ds; + } + + @SuppressWarnings("SqlNoDataSourceInspection") + public static void sampleInitFunction(Connection connection) throws SQLException { + connection.createStatement().execute("CREATE TABLE bar (\n" + + " foo VARCHAR(255)\n" + + ");"); + connection.createStatement().execute("INSERT INTO bar (foo) VALUES ('hello world');"); + connection.createStatement().execute("CREATE TABLE my_counter (\n" + + " n INT\n" + + ");"); + } +} diff --git a/modules/mariadb/src/test/java/org/testcontainers/junit/SimpleMariaDBTest.java b/modules/mariadb/src/test/java/org/testcontainers/junit/SimpleMariaDBTest.java new file mode 100644 index 00000000000..1ad65e65465 --- /dev/null +++ b/modules/mariadb/src/test/java/org/testcontainers/junit/SimpleMariaDBTest.java @@ -0,0 +1,87 @@ +package org.testcontainers.junit; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import lombok.NonNull; + +import org.apache.commons.lang.SystemUtils; +import org.junit.Test; +import org.testcontainers.containers.MariaDBContainer; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals; +import static org.rnorth.visibleassertions.VisibleAssertions.assertTrue; +import static org.junit.Assume.assumeFalse; + + +/** + * @author Miguel Gonzalez Sanchez + */ +public class SimpleMariaDBTest { + + @Test + public void testSimple() throws SQLException { + MariaDBContainer mariadb = new MariaDBContainer(); + mariadb.start(); + + try { + ResultSet resultSet = performQuery(mariadb, "SELECT 1"); + int resultSetInt = resultSet.getInt(1); + + assertEquals("A basic SELECT query succeeds", 1, resultSetInt); + } finally { + mariadb.stop(); + } + } + + @Test + public void testSpecificVersion() throws SQLException { + MariaDBContainer mariadbOldVersion = new MariaDBContainer("mariadb:5.5.51"); + mariadbOldVersion.start(); + + try { + ResultSet resultSet = performQuery(mariadbOldVersion, "SELECT VERSION()"); + String resultSetString = resultSet.getString(1); + + assertTrue("The database version can be set using a container rule parameter", resultSetString.startsWith("5.5.51")); + } finally { + mariadbOldVersion.stop(); + } + } + + @Test + public void testMariaDBWithCustomIniFile() throws SQLException { + assumeFalse(SystemUtils.IS_OS_WINDOWS); + MariaDBContainer mariadbCustomConfig = new MariaDBContainer("mariadb:10.1.16") + .withConfigurationOverride("somepath/mariadb_conf_override"); + mariadbCustomConfig.start(); + + try { + ResultSet resultSet = performQuery(mariadbCustomConfig, "SELECT @@GLOBAL.innodb_file_format"); + String result = resultSet.getString(1); + + assertEquals("The InnoDB file format has been set by the ini file content", "Barracuda", result); + } finally { + mariadbCustomConfig.stop(); + } + } + + @NonNull + protected ResultSet performQuery(MariaDBContainer containerRule, String sql) throws SQLException { + HikariConfig hikariConfig = new HikariConfig(); + hikariConfig.setJdbcUrl(containerRule.getJdbcUrl()); + hikariConfig.setUsername(containerRule.getUsername()); + hikariConfig.setPassword(containerRule.getPassword()); + + HikariDataSource ds = new HikariDataSource(hikariConfig); + Statement statement = ds.getConnection().createStatement(); + statement.execute(sql); + ResultSet resultSet = statement.getResultSet(); + + resultSet.next(); + return resultSet; + } +} diff --git a/modules/mariadb/src/test/resources/logback-test.xml b/modules/mariadb/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..ed0e5b2659e --- /dev/null +++ b/modules/mariadb/src/test/resources/logback-test.xml @@ -0,0 +1,29 @@ + + + + + + %d{HH:mm:ss.SSS} %-5level %logger - %msg%n + + + + + + + + + + + + + + + + + + + PROFILER + DENY + + \ No newline at end of file diff --git a/modules/mariadb/src/test/resources/somepath/init_mariadb.sql b/modules/mariadb/src/test/resources/somepath/init_mariadb.sql new file mode 100644 index 00000000000..2b00ee968b0 --- /dev/null +++ b/modules/mariadb/src/test/resources/somepath/init_mariadb.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/mariadb/src/test/resources/somepath/mariadb_conf_override/my.cnf b/modules/mariadb/src/test/resources/somepath/mariadb_conf_override/my.cnf new file mode 100644 index 00000000000..86ce3c30c65 --- /dev/null +++ b/modules/mariadb/src/test/resources/somepath/mariadb_conf_override/my.cnf @@ -0,0 +1,2 @@ +[mysqld] +innodb_file_format=Barracuda \ No newline at end of file diff --git a/pom.xml b/pom.xml index 78b1a43033c..117f4cc4f1e 100644 --- a/pom.xml +++ b/pom.xml @@ -61,6 +61,11 @@ Pete Cornish outofcoffee@gmail.com + + mgonzalez + Miguel Gonzalez Sanchez + miguel-gonzalez@gmx.de + @@ -161,6 +166,7 @@ modules/postgresql modules/selenium modules/nginx + modules/mariadb