diff --git a/core/src/main/java/org/testcontainers/containers/ContainerLaunchException.java b/core/src/main/java/org/testcontainers/containers/ContainerLaunchException.java index 9943d9f612b..bf407c16808 100644 --- a/core/src/main/java/org/testcontainers/containers/ContainerLaunchException.java +++ b/core/src/main/java/org/testcontainers/containers/ContainerLaunchException.java @@ -9,7 +9,7 @@ public ContainerLaunchException(String message) { super(message); } - public ContainerLaunchException(String message, Exception exception) { - super(message, exception); + public ContainerLaunchException(String message, Throwable cause) { + super(message, cause); } } diff --git a/core/src/test/resources/logback-test.xml b/core/src/test/resources/logback-test.xml index 57a53cdd3aa..5e96a70b244 100644 --- a/core/src/test/resources/logback-test.xml +++ b/core/src/test/resources/logback-test.xml @@ -14,10 +14,8 @@ - - diff --git a/docs/examples/src/test/resources/logback-test.xml b/docs/examples/src/test/resources/logback-test.xml index 49da0be9709..62bc2c43d3a 100644 --- a/docs/examples/src/test/resources/logback-test.xml +++ b/docs/examples/src/test/resources/logback-test.xml @@ -1,5 +1,4 @@ - @@ -11,17 +10,4 @@ - - - - - - - - - - - PROFILER - DENY - diff --git a/examples/disque-job-queue/src/test/resources/logback-test.xml b/examples/disque-job-queue/src/test/resources/logback-test.xml index c53f381fca5..be4ebaed2c8 100644 --- a/examples/disque-job-queue/src/test/resources/logback-test.xml +++ b/examples/disque-job-queue/src/test/resources/logback-test.xml @@ -4,15 +4,11 @@ - %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + %d{HH:mm:ss.SSS} %-5level %logger - %msg%n - + - - - - - \ No newline at end of file + diff --git a/examples/linked-container/src/test/resources/logback-test.xml b/examples/linked-container/src/test/resources/logback-test.xml index 23f40bbec32..be4ebaed2c8 100644 --- a/examples/linked-container/src/test/resources/logback-test.xml +++ b/examples/linked-container/src/test/resources/logback-test.xml @@ -1,16 +1,14 @@ + + - %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + %d{HH:mm:ss.SSS} %-5level %logger - %msg%n - + - - - - - - \ No newline at end of file + diff --git a/examples/redis-backed-cache-testng/src/test/resources/logback-test.xml b/examples/redis-backed-cache-testng/src/test/resources/logback-test.xml index c53f381fca5..be4ebaed2c8 100644 --- a/examples/redis-backed-cache-testng/src/test/resources/logback-test.xml +++ b/examples/redis-backed-cache-testng/src/test/resources/logback-test.xml @@ -4,15 +4,11 @@ - %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + %d{HH:mm:ss.SSS} %-5level %logger - %msg%n - + - - - - - \ No newline at end of file + diff --git a/examples/redis-backed-cache/src/test/resources/logback-test.xml b/examples/redis-backed-cache/src/test/resources/logback-test.xml index c53f381fca5..be4ebaed2c8 100644 --- a/examples/redis-backed-cache/src/test/resources/logback-test.xml +++ b/examples/redis-backed-cache/src/test/resources/logback-test.xml @@ -4,15 +4,11 @@ - %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + %d{HH:mm:ss.SSS} %-5level %logger - %msg%n - + - - - - - \ No newline at end of file + diff --git a/examples/selenium-container/src/test/resources/logback-test.xml b/examples/selenium-container/src/test/resources/logback-test.xml index c53f381fca5..be4ebaed2c8 100644 --- a/examples/selenium-container/src/test/resources/logback-test.xml +++ b/examples/selenium-container/src/test/resources/logback-test.xml @@ -4,15 +4,11 @@ - %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + %d{HH:mm:ss.SSS} %-5level %logger - %msg%n - + - - - - - \ No newline at end of file + diff --git a/examples/spock/src/test/resources/logback-test.xml b/examples/spock/src/test/resources/logback-test.xml index 6e7cfd092de..535e406fc13 100644 --- a/examples/spock/src/test/resources/logback-test.xml +++ b/examples/spock/src/test/resources/logback-test.xml @@ -12,16 +12,5 @@ - - - - - - - - - - PROFILER - DENY - + diff --git a/examples/spring-boot/src/test/resources/logback-test.xml b/examples/spring-boot/src/test/resources/logback-test.xml index 86c4c861a78..5431cb6200b 100644 --- a/examples/spring-boot/src/test/resources/logback-test.xml +++ b/examples/spring-boot/src/test/resources/logback-test.xml @@ -1,8 +1,3 @@ - - - - - - \ No newline at end of file + diff --git a/modules/cassandra/src/test/resources/logback-test.xml b/modules/cassandra/src/test/resources/logback-test.xml index fc48b14c7de..535e406fc13 100644 --- a/modules/cassandra/src/test/resources/logback-test.xml +++ b/modules/cassandra/src/test/resources/logback-test.xml @@ -13,5 +13,4 @@ - - \ No newline at end of file + diff --git a/modules/couchbase/src/test/resources/logback-test.xml b/modules/couchbase/src/test/resources/logback-test.xml index d163f3ececb..535e406fc13 100644 --- a/modules/couchbase/src/test/resources/logback-test.xml +++ b/modules/couchbase/src/test/resources/logback-test.xml @@ -8,18 +8,9 @@ - + - - - - - - - - - - + diff --git a/modules/dynalite/src/test/resources/logback-test.xml b/modules/dynalite/src/test/resources/logback-test.xml index 5d27573a3cb..535e406fc13 100644 --- a/modules/dynalite/src/test/resources/logback-test.xml +++ b/modules/dynalite/src/test/resources/logback-test.xml @@ -8,17 +8,9 @@ - + - - - - - - - - - - \ No newline at end of file + + diff --git a/modules/elasticsearch/src/test/resources/logback-test.xml b/modules/elasticsearch/src/test/resources/logback-test.xml index 6d3eb183989..535e406fc13 100644 --- a/modules/elasticsearch/src/test/resources/logback-test.xml +++ b/modules/elasticsearch/src/test/resources/logback-test.xml @@ -8,10 +8,9 @@ - + - - + diff --git a/modules/influxdb/src/test/resources/logback-test.xml b/modules/influxdb/src/test/resources/logback-test.xml index e8f35ff1c79..535e406fc13 100644 --- a/modules/influxdb/src/test/resources/logback-test.xml +++ b/modules/influxdb/src/test/resources/logback-test.xml @@ -8,17 +8,9 @@ - + - - - - - - - - - + diff --git a/modules/jdbc-test/src/test/java/org/testcontainers/junit/AbstractContainerDatabaseTest.java b/modules/jdbc-test/src/test/java/org/testcontainers/junit/AbstractContainerDatabaseTest.java index a01146fe7a3..7230759382d 100644 --- a/modules/jdbc-test/src/test/java/org/testcontainers/junit/AbstractContainerDatabaseTest.java +++ b/modules/jdbc-test/src/test/java/org/testcontainers/junit/AbstractContainerDatabaseTest.java @@ -2,15 +2,20 @@ import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; +import org.junit.After; import org.testcontainers.containers.JdbcDatabaseContainer; import javax.sql.DataSource; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.util.HashSet; +import java.util.Set; abstract class AbstractContainerDatabaseTest { + private final Set datasourcesForCleanup = new HashSet<>(); + ResultSet performQuery(JdbcDatabaseContainer container, String sql) throws SQLException { DataSource ds = getDataSource(container); Statement statement = ds.getConnection().createStatement(); @@ -26,7 +31,16 @@ DataSource getDataSource(JdbcDatabaseContainer container) { hikariConfig.setJdbcUrl(container.getJdbcUrl()); hikariConfig.setUsername(container.getUsername()); hikariConfig.setPassword(container.getPassword()); + hikariConfig.setDriverClassName(container.getDriverClassName()); + + final HikariDataSource dataSource = new HikariDataSource(hikariConfig); + datasourcesForCleanup.add(dataSource); + + return dataSource; + } - return new HikariDataSource(hikariConfig); + @After + public void teardown() { + datasourcesForCleanup.forEach(HikariDataSource::close); } } diff --git a/modules/jdbc-test/src/test/resources/logback-test.xml b/modules/jdbc-test/src/test/resources/logback-test.xml index b0f6b00e3e0..535e406fc13 100644 --- a/modules/jdbc-test/src/test/resources/logback-test.xml +++ b/modules/jdbc-test/src/test/resources/logback-test.xml @@ -8,18 +8,9 @@ - + - - - - - - - - - - + diff --git a/modules/jdbc/src/main/java/org/testcontainers/containers/JdbcDatabaseContainer.java b/modules/jdbc/src/main/java/org/testcontainers/containers/JdbcDatabaseContainer.java index e480070168f..36a6461aa74 100644 --- a/modules/jdbc/src/main/java/org/testcontainers/containers/JdbcDatabaseContainer.java +++ b/modules/jdbc/src/main/java/org/testcontainers/containers/JdbcDatabaseContainer.java @@ -1,11 +1,8 @@ package org.testcontainers.containers; -import lombok.NonNull; import com.github.dockerjava.api.command.InspectContainerResponse; +import lombok.NonNull; import org.jetbrains.annotations.NotNull; -import org.rnorth.ducttape.ratelimits.RateLimiter; -import org.rnorth.ducttape.ratelimits.RateLimiterBuilder; -import org.rnorth.ducttape.unreliables.Unreliables; import org.testcontainers.containers.traits.LinkableContainer; import org.testcontainers.delegate.DatabaseDelegate; import org.testcontainers.ext.ScriptUtils; @@ -19,7 +16,6 @@ import java.util.Map; import java.util.Properties; import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; /** * Base class for containers that expose a JDBC connection @@ -33,11 +29,6 @@ public abstract class JdbcDatabaseContainer parameters = new HashMap<>(); - private static final RateLimiter DB_CONNECT_RATE_LIMIT = RateLimiterBuilder.newBuilder() - .withRate(10, TimeUnit.SECONDS) - .withConstantThroughput() - .build(); - private int startupTimeoutSeconds = 120; private int connectTimeoutSeconds = 120; @@ -123,26 +114,39 @@ public SELF withInitScript(String initScriptPath) { @Override protected void waitUntilContainerStarted() { - // Repeatedly try and open a connection to the DB and execute a test query - logger().info("Waiting for database connection to become available at {} using query '{}'", getJdbcUrl(), getTestQueryString()); - Unreliables.retryUntilSuccess(getStartupTimeoutSeconds(), TimeUnit.SECONDS, () -> { - if (!isRunning()) { - throw new ContainerLaunchException("Container failed to start"); - } - - try (Connection connection = createConnection("")) { - boolean success = connection.createStatement().execute(JdbcDatabaseContainer.this.getTestQueryString()); - - if (success) { - logger().info("Obtained a connection to container ({})", JdbcDatabaseContainer.this.getJdbcUrl()); - return null; - } else { - throw new SQLException("Failed to execute test query"); + // Repeatedly try and open a connection to the DB and execute a test query + long start = System.currentTimeMillis(); + try { + while (System.currentTimeMillis() < start + (1000 * startupTimeoutSeconds)) { + try { + if (!isRunning()) { + Thread.sleep(100L); + continue; // Don't attempt to connect yet + } + + try (Connection connection = createConnection("")) { + boolean testQuerySucceeded = connection.createStatement().execute(this.getTestQueryString()); + if (testQuerySucceeded) { + break; + } + } + } catch (NoDriverFoundException e) { + // we explicitly want this exception to fail fast without retries + throw e; + } catch (Exception e) { + // ignore so that we can try again + logger().debug("Failure when trying test query", e); + Thread.sleep(100L); } } - }); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ContainerLaunchException("Container startup wait was interrupted", e); + } + + logger().info("Container is started (JDBC URL: {})", JdbcDatabaseContainer.this.getJdbcUrl()); } @Override @@ -155,14 +159,14 @@ protected void containerIsStarted(InspectContainerResponse containerInfo) { * * @return a JDBC Driver */ - public Driver getJdbcDriverInstance() { + public Driver getJdbcDriverInstance() throws NoDriverFoundException { synchronized (DRIVER_LOAD_MUTEX) { if (driver == null) { try { driver = (Driver) Class.forName(this.getDriverClassName()).newInstance(); } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { - throw new RuntimeException("Could not get Driver", e); + throw new NoDriverFoundException("Could not get Driver", e); } } } @@ -178,7 +182,7 @@ public Driver getJdbcDriverInstance() { * @return a Connection * @throws SQLException if there is a repeated failure to create the connection */ - public Connection createConnection(String queryString) throws SQLException { + public Connection createConnection(String queryString) throws SQLException, NoDriverFoundException { final Properties info = new Properties(); info.put("user", this.getUsername()); info.put("password", this.getPassword()); @@ -186,13 +190,24 @@ public Connection createConnection(String queryString) throws SQLException { final Driver jdbcDriverInstance = getJdbcDriverInstance(); + SQLException lastException = null; try { - return Unreliables.retryUntilSuccess(getConnectTimeoutSeconds(), TimeUnit.SECONDS, () -> - DB_CONNECT_RATE_LIMIT.getWhenReady(() -> - jdbcDriverInstance.connect(url, info))); - } catch (Exception e) { - throw new SQLException("Could not create new connection", e); + long start = System.currentTimeMillis(); + // give up if we hit the time limit or the container stops running for some reason + while (System.currentTimeMillis() < start + (1000 * connectTimeoutSeconds) && isRunning()) { + try { + logger().debug("Trying to create JDBC connection using {} to {} with properties: {}", driver.getClass().getName(), url, info); + + return jdbcDriverInstance.connect(url, info); + } catch (SQLException e) { + lastException = e; + Thread.sleep(100L); + } + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); } + throw new SQLException("Could not create new connection", lastException); } /** @@ -256,4 +271,10 @@ protected int getConnectTimeoutSeconds() { protected DatabaseDelegate getDatabaseDelegate() { return new JdbcDatabaseDelegate(this, ""); } + + public static class NoDriverFoundException extends RuntimeException { + public NoDriverFoundException(String message, Throwable e) { + super(message, e); + } + } } diff --git a/modules/jdbc/src/test/java/org/testcontainers/jdbc/MissingJdbcDriverTest.java b/modules/jdbc/src/test/java/org/testcontainers/jdbc/MissingJdbcDriverTest.java new file mode 100644 index 00000000000..38ae38095eb --- /dev/null +++ b/modules/jdbc/src/test/java/org/testcontainers/jdbc/MissingJdbcDriverTest.java @@ -0,0 +1,86 @@ +package org.testcontainers.jdbc; + +import com.google.common.base.Throwables; +import org.junit.Test; +import org.testcontainers.containers.JdbcDatabaseContainer; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals; +import static org.rnorth.visibleassertions.VisibleAssertions.assertTrue; +import static org.rnorth.visibleassertions.VisibleAssertions.fail; + +public class MissingJdbcDriverTest { + + @Test + public void shouldFailFastIfNoDriverFound() { + + final MissingDriverContainer container = new MissingDriverContainer(); + + try { + container.start(); + fail("The container is expected to fail to start"); + } catch (Exception e) { + final Throwable rootCause = Throwables.getRootCause(e); + assertTrue("ClassNotFoundException is the root cause", rootCause instanceof ClassNotFoundException); + } finally { + container.stop(); + } + + assertEquals("only one connection attempt should have been made", 1, container.getConnectionAttempts()); + } + + /** + * Container class for the purposes of testing, with a known non-existent driver + */ + static class MissingDriverContainer extends JdbcDatabaseContainer { + private final AtomicInteger connectionAttempts = new AtomicInteger(); + + MissingDriverContainer() { + super("mysql:5.7.22"); + withEnv("MYSQL_ROOT_PASSWORD", "test"); + withExposedPorts(3306); + } + + @Override + public String getDriverClassName() { + return "nonexistent.ClassName"; + } + + @Override + public String getJdbcUrl() { + return ""; + } + + @Override + public String getUsername() { + return "root"; + } + + @Override + public String getPassword() { + return "test"; + } + + @Override + protected String getTestQueryString() { + return ""; + } + + @Override + public Connection createConnection(String queryString) throws SQLException, NoDriverFoundException { + connectionAttempts.incrementAndGet(); // + return super.createConnection(queryString); + } + + /** + * test window + * @return how many times a connection was attempted + */ + int getConnectionAttempts() { + return connectionAttempts.get(); + } + } +} diff --git a/modules/jdbc/src/test/resources/logback-test.xml b/modules/jdbc/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..535e406fc13 --- /dev/null +++ b/modules/jdbc/src/test/resources/logback-test.xml @@ -0,0 +1,16 @@ + + + + + + %d{HH:mm:ss.SSS} %-5level %logger - %msg%n + + + + + + + + + diff --git a/modules/junit-jupiter/src/test/resources/logback-test.xml b/modules/junit-jupiter/src/test/resources/logback-test.xml index 1d1a7695915..535e406fc13 100644 --- a/modules/junit-jupiter/src/test/resources/logback-test.xml +++ b/modules/junit-jupiter/src/test/resources/logback-test.xml @@ -8,8 +8,9 @@ - + - \ No newline at end of file + + diff --git a/modules/kafka/src/test/resources/logback-test.xml b/modules/kafka/src/test/resources/logback-test.xml index 7bd6a94d827..535e406fc13 100644 --- a/modules/kafka/src/test/resources/logback-test.xml +++ b/modules/kafka/src/test/resources/logback-test.xml @@ -13,6 +13,4 @@ - - - \ No newline at end of file + diff --git a/modules/localstack/src/test/resources/logback-test.xml b/modules/localstack/src/test/resources/logback-test.xml index cc69fdf0ce0..535e406fc13 100644 --- a/modules/localstack/src/test/resources/logback-test.xml +++ b/modules/localstack/src/test/resources/logback-test.xml @@ -8,20 +8,9 @@ - + - - - - - - - - - - - - - \ No newline at end of file + + diff --git a/modules/neo4j/src/test/resources/logback-test.xml b/modules/neo4j/src/test/resources/logback-test.xml index 7bd6a94d827..535e406fc13 100644 --- a/modules/neo4j/src/test/resources/logback-test.xml +++ b/modules/neo4j/src/test/resources/logback-test.xml @@ -13,6 +13,4 @@ - - - \ No newline at end of file + diff --git a/modules/nginx/src/test/resources/logback-test.xml b/modules/nginx/src/test/resources/logback-test.xml index b0f6b00e3e0..535e406fc13 100644 --- a/modules/nginx/src/test/resources/logback-test.xml +++ b/modules/nginx/src/test/resources/logback-test.xml @@ -8,18 +8,9 @@ - + - - - - - - - - - - + diff --git a/modules/oracle-xe/src/test/resources/logback-test.xml b/modules/oracle-xe/src/test/resources/logback-test.xml index b0f6b00e3e0..535e406fc13 100644 --- a/modules/oracle-xe/src/test/resources/logback-test.xml +++ b/modules/oracle-xe/src/test/resources/logback-test.xml @@ -8,18 +8,9 @@ - + - - - - - - - - - - + diff --git a/modules/postgresql/src/main/java/org/testcontainers/containers/PostgreSQLContainer.java b/modules/postgresql/src/main/java/org/testcontainers/containers/PostgreSQLContainer.java index eb7b78b1864..a9429210261 100644 --- a/modules/postgresql/src/main/java/org/testcontainers/containers/PostgreSQLContainer.java +++ b/modules/postgresql/src/main/java/org/testcontainers/containers/PostgreSQLContainer.java @@ -59,7 +59,8 @@ public String getDriverClassName() { @Override public String getJdbcUrl() { - return "jdbc:postgresql://" + getContainerIpAddress() + ":" + getMappedPort(POSTGRESQL_PORT) + "/" + databaseName; + // Disable Postgres driver use of java.util.logging to reduce noise at startup time + return "jdbc:postgresql://" + getContainerIpAddress() + ":" + getMappedPort(POSTGRESQL_PORT) + "/" + databaseName + "?loggerLevel=OFF"; } @Override diff --git a/modules/pulsar/src/test/resources/logback-test.xml b/modules/pulsar/src/test/resources/logback-test.xml index 7bd6a94d827..535e406fc13 100644 --- a/modules/pulsar/src/test/resources/logback-test.xml +++ b/modules/pulsar/src/test/resources/logback-test.xml @@ -13,6 +13,4 @@ - - - \ No newline at end of file + diff --git a/modules/selenium/src/test/resources/logback-test.xml b/modules/selenium/src/test/resources/logback-test.xml index b0f6b00e3e0..535e406fc13 100644 --- a/modules/selenium/src/test/resources/logback-test.xml +++ b/modules/selenium/src/test/resources/logback-test.xml @@ -8,18 +8,9 @@ - + - - - - - - - - - - + diff --git a/modules/spock/src/test/resources/logback-test.xml b/modules/spock/src/test/resources/logback-test.xml index 1d1a7695915..535e406fc13 100644 --- a/modules/spock/src/test/resources/logback-test.xml +++ b/modules/spock/src/test/resources/logback-test.xml @@ -8,8 +8,9 @@ - + - \ No newline at end of file + + diff --git a/modules/vault/src/test/resources/logback-test.xml b/modules/vault/src/test/resources/logback-test.xml index 5d27573a3cb..535e406fc13 100644 --- a/modules/vault/src/test/resources/logback-test.xml +++ b/modules/vault/src/test/resources/logback-test.xml @@ -8,17 +8,9 @@ - + - - - - - - - - - - \ No newline at end of file + +