diff options
Diffstat (limited to 'src/test/java/com/zaxxer/hikari/pool')
26 files changed, 4446 insertions, 0 deletions
diff --git a/src/test/java/com/zaxxer/hikari/pool/ConcurrentCloseConnectionTest.java b/src/test/java/com/zaxxer/hikari/pool/ConcurrentCloseConnectionTest.java new file mode 100644 index 0000000..8820b79 --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/pool/ConcurrentCloseConnectionTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2013, 2014 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package com.zaxxer.hikari.pool; + +import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import org.junit.Test; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; + +/** + * @author Matthew Tambara (matthew.tambara@liferay.com) + */ +public class ConcurrentCloseConnectionTest +{ + @Test + public void testConcurrentClose() throws Exception + { + HikariConfig config = newHikariConfig(); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (HikariDataSource ds = new HikariDataSource(config); + final Connection connection = ds.getConnection()) { + + ExecutorService executorService = Executors.newFixedThreadPool(10); + + List<Future<?>> futures = new ArrayList<>(); + + for (int i = 0; i < 500; i++) { + final PreparedStatement preparedStatement = + connection.prepareStatement(""); + + futures.add(executorService.submit(new Callable<Void>() { + + @Override + public Void call() throws Exception { + preparedStatement.close(); + + return null; + } + + })); + } + + executorService.shutdown(); + + for (Future<?> future : futures) { + future.get(); + } + } + } +} diff --git a/src/test/java/com/zaxxer/hikari/pool/ConnectionPoolSizeVsThreadsTest.java b/src/test/java/com/zaxxer/hikari/pool/ConnectionPoolSizeVsThreadsTest.java new file mode 100755 index 0000000..e66ba58 --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/pool/ConnectionPoolSizeVsThreadsTest.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2013, 2017 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package com.zaxxer.hikari.pool; + +import static com.zaxxer.hikari.pool.TestElf.getPool; +import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; +import static com.zaxxer.hikari.util.ClockSource.currentTime; +import static com.zaxxer.hikari.util.ClockSource.elapsedMillis; +import static com.zaxxer.hikari.util.UtilityElf.quietlySleep; +import static java.util.concurrent.Executors.newFixedThreadPool; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.sql.Connection; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import com.zaxxer.hikari.mocks.StubDataSource; + +/** + * @author Matthew Tambara (matthew.tambara@liferay.com) + */ +public class ConnectionPoolSizeVsThreadsTest { + + public static final Logger LOGGER = LoggerFactory.getLogger(ConnectionPoolSizeVsThreadsTest.class); + + public static final int ITERATIONS = 50_000; + + @Test + public void testPoolSizeAboutSameSizeAsThreadCount() throws Exception { + final int threadCount = 50; + final Counts counts = testPoolSize(2 /*minIdle*/, + 100 /*maxPoolSize*/, + threadCount, + 1 /*workTimeMs*/, + 0 /*restTimeMs*/, + 20 /*connectionAcquisitionTimeMs*/, + ITERATIONS, + SECONDS.toMillis(2) /*postTestTimeMs*/); + + // maxActive may never make it to threadCount but it shouldn't be any higher + assertEquals(threadCount, counts.maxActive, 15 /*delta*/); + assertEquals(threadCount, counts.maxTotal, 5 /*delta*/); + } + + @Test + public void testSlowConnectionTimeBurstyWork() throws Exception { + // setup a bursty work load, 50 threads all needing to do around 100 units of work. + // Using a more realistic time for connection startup of 250 ms and only 5 seconds worth of work will mean that we end up finishing + // all of the work before we actually have setup 50 connections even though we have requested 50 connections + final int threadCount = 50; + final int workItems = threadCount * 100; + final int workTimeMs = 0; + final int connectionAcquisitionTimeMs = 250; + final Counts counts = testPoolSize(2 /*minIdle*/, + 100 /*maxPoolSize*/, + threadCount, + workTimeMs, + 0 /*restTimeMs*/, + connectionAcquisitionTimeMs, + workItems /*iterations*/, + SECONDS.toMillis(3) /*postTestTimeMs*/); + + // hard to put exact bounds on how many thread we will use but we can put an upper bound on usage (if there was only one thread) + final long totalWorkTime = workItems * workTimeMs; + final long connectionMax = totalWorkTime / connectionAcquisitionTimeMs; + assertTrue(connectionMax <= counts.maxActive); + assertEquals(connectionMax, counts.maxTotal, 2 + 2 /*delta*/); + } + + private Counts testPoolSize(final int minIdle, final int maxPoolSize, final int threadCount, + final long workTimeMs, final long restTimeMs, final long connectionAcquisitionTimeMs, + final int iterations, final long postTestTimeMs) throws Exception { + + LOGGER.info("Starting test (minIdle={}, maxPoolSize={}, threadCount={}, workTimeMs={}, restTimeMs={}, connectionAcquisitionTimeMs={}, iterations={}, postTestTimeMs={})", + minIdle, maxPoolSize, threadCount, workTimeMs, restTimeMs, connectionAcquisitionTimeMs, iterations, postTestTimeMs); + + final HikariConfig config = newHikariConfig(); + config.setMinimumIdle(minIdle); + config.setMaximumPoolSize(maxPoolSize); + config.setInitializationFailTimeout(Long.MAX_VALUE); + config.setConnectionTimeout(2500); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + final AtomicReference<Exception> ref = new AtomicReference<>(null); + + // Initialize HikariPool with no initial connections and room to grow + try (final HikariDataSource ds = new HikariDataSource(config)) { + final StubDataSource stubDataSource = ds.unwrap(StubDataSource.class); + // connection acquisition takes more than 0 ms in a real system + stubDataSource.setConnectionAcquistionTime(connectionAcquisitionTimeMs); + + final ExecutorService threadPool = newFixedThreadPool(threadCount); + final CountDownLatch allThreadsDone = new CountDownLatch(iterations); + for (int i = 0; i < iterations; i++) { + threadPool.submit(() -> { + if (ref.get() == null) { + quietlySleep(restTimeMs); + try (Connection c2 = ds.getConnection()) { + quietlySleep(workTimeMs); + } + catch (Exception e) { + ref.set(e); + } + } + allThreadsDone.countDown(); + }); + } + + final HikariPool pool = getPool(ds); + + // collect pool usage data while work is still being done + final Counts underLoad = new Counts(); + while (allThreadsDone.getCount() > 0 || pool.getTotalConnections() < minIdle) { + quietlySleep(50); + underLoad.updateMaxCounts(pool); + } + + // wait for long enough any pending acquisitions have already been done + LOGGER.info("Test Over, waiting for post delay time {}ms ", postTestTimeMs); + quietlySleep(connectionAcquisitionTimeMs + workTimeMs + restTimeMs); + + // collect pool data while there is no work to do. + final Counts postLoad = new Counts(); + final long start = currentTime(); + while (elapsedMillis(start) < postTestTimeMs) { + quietlySleep(50); + postLoad.updateMaxCounts(pool); + } + + allThreadsDone.await(); + + threadPool.shutdown(); + threadPool.awaitTermination(30, SECONDS); + + if (ref.get() != null) { + LOGGER.error("Task failed", ref.get()); + fail("Task failed"); + } + + LOGGER.info("Under Load... {}", underLoad); + LOGGER.info("Post Load.... {}", postLoad); + + // verify that the no connections created after the work has stopped + if (postTestTimeMs > 0) { + if (postLoad.maxActive != 0) { + fail("Max Active was greater than 0 after test was done"); + } + + final int createdAfterWorkAllFinished = postLoad.maxTotal - underLoad.maxTotal; + assertEquals("Connections were created when there was no waiting consumers", 0, createdAfterWorkAllFinished, 1 /*delta*/); + } + + return underLoad; + } + } + + private static class Counts { + int maxTotal = 0; + int maxActive = 0; + + void updateMaxCounts(final HikariPool pool) { + maxTotal = Math.max(pool.getTotalConnections(), maxTotal); + maxActive = Math.max(pool.getActiveConnections(), maxActive); + } + + @Override + public String toString() { + return "Counts{" + + "maxTotal=" + maxTotal + + ", maxActive=" + maxActive + + '}'; + } + } +} diff --git a/src/test/java/com/zaxxer/hikari/pool/ConnectionRaceConditionTest.java b/src/test/java/com/zaxxer/hikari/pool/ConnectionRaceConditionTest.java new file mode 100755 index 0000000..31f8363 --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/pool/ConnectionRaceConditionTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2013, 2014 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package com.zaxxer.hikari.pool; + +import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; +import static com.zaxxer.hikari.pool.TestElf.setSlf4jLogLevel; +import static org.junit.Assert.fail; + +import java.sql.Connection; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.logging.log4j.Level; +import org.junit.After; +import org.junit.Test; +import org.slf4j.LoggerFactory; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import com.zaxxer.hikari.util.ConcurrentBag; + +/** + * @author Matthew Tambara (matthew.tambara@liferay.com) + */ +public class ConnectionRaceConditionTest +{ + + public static final int ITERATIONS = 10_000; + + @Test + public void testRaceCondition() throws Exception + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(0); + config.setMaximumPoolSize(10); + config.setInitializationFailTimeout(Long.MAX_VALUE); + config.setConnectionTimeout(2500); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + setSlf4jLogLevel(ConcurrentBag.class, Level.INFO); + + final AtomicReference<Exception> ref = new AtomicReference<>(null); + + // Initialize HikariPool with no initial connections and room to grow + try (final HikariDataSource ds = new HikariDataSource(config)) { + ExecutorService threadPool = Executors.newFixedThreadPool(2); + for (int i = 0; i < ITERATIONS; i++) { + threadPool.submit(new Callable<Exception>() { + /** {@inheritDoc} */ + @Override + public Exception call() throws Exception + { + if (ref.get() == null) { + Connection c2; + try { + c2 = ds.getConnection(); + ds.evictConnection(c2); + } + catch (Exception e) { + ref.set(e); + } + } + return null; + } + }); + } + + threadPool.shutdown(); + threadPool.awaitTermination(30, TimeUnit.SECONDS); + + if (ref.get() != null) { + LoggerFactory.getLogger(ConnectionRaceConditionTest.class).error("Task failed", ref.get()); + fail("Task failed"); + } + } + catch (Exception e) { + throw e; + } + } + + @After + public void after() + { + System.getProperties().remove("com.zaxxer.hikari.housekeeping.periodMs"); + + setSlf4jLogLevel(HikariPool.class, Level.WARN); + setSlf4jLogLevel(ConcurrentBag.class, Level.WARN); + } +} diff --git a/src/test/java/com/zaxxer/hikari/pool/ConnectionStateTest.java b/src/test/java/com/zaxxer/hikari/pool/ConnectionStateTest.java new file mode 100644 index 0000000..35b36fb --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/pool/ConnectionStateTest.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2013, 2014 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package com.zaxxer.hikari.pool; + +import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; +import static com.zaxxer.hikari.pool.TestElf.newHikariDataSource; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.junit.Test; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import com.zaxxer.hikari.util.UtilityElf; + +public class ConnectionStateTest +{ + @Test + public void testAutoCommit() throws SQLException + { + try (HikariDataSource ds = newHikariDataSource()) { + ds.setAutoCommit(true); + ds.setMinimumIdle(1); + ds.setMaximumPoolSize(1); + ds.setConnectionTestQuery("VALUES 1"); + ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + ds.addDataSourceProperty("user", "bar"); + ds.addDataSourceProperty("password", "secret"); + ds.addDataSourceProperty("url", "baf"); + ds.addDataSourceProperty("loginTimeout", "10"); + + try (Connection connection = ds.getConnection()) { + Connection unwrap = connection.unwrap(Connection.class); + connection.setAutoCommit(false); + connection.close(); + + assertTrue(unwrap.getAutoCommit()); + } + } + } + + @Test + public void testTransactionIsolation() throws SQLException + { + try (HikariDataSource ds = newHikariDataSource()) { + ds.setTransactionIsolation("TRANSACTION_READ_COMMITTED"); + ds.setMinimumIdle(1); + ds.setMaximumPoolSize(1); + ds.setConnectionTestQuery("VALUES 1"); + ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (Connection connection = ds.getConnection()) { + Connection unwrap = connection.unwrap(Connection.class); + connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); + connection.close(); + + assertEquals(Connection.TRANSACTION_READ_COMMITTED, unwrap.getTransactionIsolation()); + } + } + } + + @Test + public void testIsolation() throws Exception + { + HikariConfig config = newHikariConfig(); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + config.setTransactionIsolation("TRANSACTION_REPEATABLE_READ"); + config.validate(); + + int transactionIsolation = UtilityElf.getTransactionIsolation(config.getTransactionIsolation()); + assertSame(Connection.TRANSACTION_REPEATABLE_READ, transactionIsolation); + } + + @Test + public void testReadOnly() throws Exception + { + try (HikariDataSource ds = newHikariDataSource()) { + ds.setCatalog("test"); + ds.setMinimumIdle(1); + ds.setMaximumPoolSize(1); + ds.setConnectionTestQuery("VALUES 1"); + ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (Connection connection = ds.getConnection()) { + Connection unwrap = connection.unwrap(Connection.class); + connection.setReadOnly(true); + connection.close(); + + assertFalse(unwrap.isReadOnly()); + } + } + } + + @Test + public void testCatalog() throws SQLException + { + try (HikariDataSource ds = newHikariDataSource()) { + ds.setCatalog("test"); + ds.setMinimumIdle(1); + ds.setMaximumPoolSize(1); + ds.setConnectionTestQuery("VALUES 1"); + ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (Connection connection = ds.getConnection()) { + Connection unwrap = connection.unwrap(Connection.class); + connection.setCatalog("other"); + connection.close(); + + assertEquals("test", unwrap.getCatalog()); + } + } + } + + @Test + public void testCommitTracking() throws SQLException + { + try (HikariDataSource ds = newHikariDataSource()) { + ds.setAutoCommit(false); + ds.setMinimumIdle(1); + ds.setMaximumPoolSize(1); + ds.setConnectionTestQuery("VALUES 1"); + ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (Connection connection = ds.getConnection()) { + Statement statement = connection.createStatement(); + statement.execute("SELECT something"); + assertTrue(TestElf.getConnectionCommitDirtyState(connection)); + + connection.commit(); + assertFalse(TestElf.getConnectionCommitDirtyState(connection)); + + statement.execute("SELECT something", Statement.NO_GENERATED_KEYS); + assertTrue(TestElf.getConnectionCommitDirtyState(connection)); + + connection.rollback(); + assertFalse(TestElf.getConnectionCommitDirtyState(connection)); + + ResultSet resultSet = statement.executeQuery("SELECT something"); + assertTrue(TestElf.getConnectionCommitDirtyState(connection)); + + connection.rollback(null); + assertFalse(TestElf.getConnectionCommitDirtyState(connection)); + + resultSet.updateRow(); + assertTrue(TestElf.getConnectionCommitDirtyState(connection)); + } + } + } +} diff --git a/src/test/java/com/zaxxer/hikari/pool/ExceptionTest.java b/src/test/java/com/zaxxer/hikari/pool/ExceptionTest.java new file mode 100644 index 0000000..8bd64b0 --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/pool/ExceptionTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2013, 2014 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package com.zaxxer.hikari.pool; + +import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; +import static com.zaxxer.hikari.pool.TestElf.getPool; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; + +public class ExceptionTest +{ + private HikariDataSource ds; + + @Before + public void setup() + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(1); + config.setMaximumPoolSize(2); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + ds = new HikariDataSource(config); + } + + @After + public void teardown() + { + ds.close(); + } + + @Test + public void testException1() throws SQLException + { + try (Connection connection = ds.getConnection()) { + assertNotNull(connection); + + PreparedStatement statement = connection.prepareStatement("SELECT some, thing FROM somewhere WHERE something=?"); + assertNotNull(statement); + + ResultSet resultSet = statement.executeQuery(); + assertNotNull(resultSet); + + try { + statement.getMaxFieldSize(); + fail(); + } + catch (Exception e) { + assertSame(SQLException.class, e.getClass()); + } + } + + HikariPool pool = getPool(ds); + assertTrue("Total (3) connections not as expected", pool.getTotalConnections() >= 0); + assertTrue("Idle (3) connections not as expected", pool.getIdleConnections() >= 0); + } + + @Test + public void testUseAfterStatementClose() throws SQLException + { + Connection connection = ds.getConnection(); + assertNotNull(connection); + + try (Statement statement = connection.prepareStatement("SELECT some, thing FROM somewhere WHERE something=?")) { + statement.close(); + statement.getMoreResults(); + + fail(); + } + catch (SQLException e) { + assertSame("Connection is closed", e.getMessage()); + } + } + + @Test + public void testUseAfterClose() throws SQLException + { + try (Connection connection = ds.getConnection()) { + assertNotNull(connection); + connection.close(); + + try (Statement statement = connection.prepareStatement("SELECT some, thing FROM somewhere WHERE something=?")) { + fail(); + } + catch (SQLException e) { + assertSame("Connection is closed", e.getMessage()); + } + } + } +} diff --git a/src/test/java/com/zaxxer/hikari/pool/HouseKeeperCleanupTest.java b/src/test/java/com/zaxxer/hikari/pool/HouseKeeperCleanupTest.java new file mode 100644 index 0000000..1a7caa7 --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/pool/HouseKeeperCleanupTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2013, 2014 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package com.zaxxer.hikari.pool; + +import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; +import static org.junit.Assert.assertEquals; + +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import com.zaxxer.hikari.util.UtilityElf; + +/** + * @author Martin Stříž (striz@raynet.cz) + */ +public class HouseKeeperCleanupTest +{ + + private ScheduledThreadPoolExecutor executor; + + @Before + public void before() throws Exception + { + ThreadFactory threadFactory = new UtilityElf.DefaultThreadFactory("global housekeeper", true); + + executor = new ScheduledThreadPoolExecutor(1, threadFactory, new ThreadPoolExecutor.DiscardPolicy()); + executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); + executor.setRemoveOnCancelPolicy(true); + } + + @Test + public void testHouseKeeperCleanupWithCustomExecutor() throws Exception + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(0); + config.setMaximumPoolSize(10); + config.setInitializationFailTimeout(Long.MAX_VALUE); + config.setConnectionTimeout(2500); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + config.setScheduledExecutorService(executor); + + HikariConfig config2 = newHikariConfig(); + config.copyState(config2); + + try ( + final HikariDataSource ds1 = new HikariDataSource(config); + final HikariDataSource ds2 = new HikariDataSource(config2) + ) { + assertEquals("Scheduled tasks count not as expected, ", 2, executor.getQueue().size()); + } + + assertEquals("Scheduled tasks count not as expected, ", 0, executor.getQueue().size()); + } + + @After + public void after() throws Exception + { + executor.shutdown(); + executor.awaitTermination(5, TimeUnit.SECONDS); + } + +} diff --git a/src/test/java/com/zaxxer/hikari/pool/IsolationTest.java b/src/test/java/com/zaxxer/hikari/pool/IsolationTest.java new file mode 100644 index 0000000..01a80ae --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/pool/IsolationTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2013, 2014 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package com.zaxxer.hikari.pool; + +import static com.zaxxer.hikari.pool.TestElf.newHikariDataSource; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; + +import java.sql.Connection; +import java.sql.SQLException; + +import org.junit.Test; + +import com.zaxxer.hikari.HikariDataSource; + +public class IsolationTest +{ + @Test + public void testIsolation() throws SQLException + { + try (HikariDataSource ds = newHikariDataSource()) { + ds.setMinimumIdle(1); + ds.setMaximumPoolSize(1); + ds.setIsolateInternalQueries(true); + ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (Connection connection = ds.getConnection()) { + connection.close(); + + try (Connection connection2 = ds.getConnection()) { + connection2.close(); + + assertNotSame(connection, connection2); + assertSame(connection.unwrap(Connection.class), connection2.unwrap(Connection.class)); + } + } + } + } + + @Test + public void testNonIsolation() throws SQLException + { + try (HikariDataSource ds = newHikariDataSource()) { + ds.setMinimumIdle(1); + ds.setMaximumPoolSize(1); + ds.setIsolateInternalQueries(false); + ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (Connection connection = ds.getConnection()) { + connection.close(); + + try (Connection connection2 = ds.getConnection()) { + connection2.close(); + + assertSame(connection.unwrap(Connection.class), connection2.unwrap(Connection.class)); + } + } + } + } +} diff --git a/src/test/java/com/zaxxer/hikari/pool/JdbcDriverTest.java b/src/test/java/com/zaxxer/hikari/pool/JdbcDriverTest.java new file mode 100644 index 0000000..0d9cc2f --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/pool/JdbcDriverTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2014 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zaxxer.hikari.pool; + +import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.sql.Connection; +import java.sql.SQLException; + +import org.junit.After; +import org.junit.Test; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import com.zaxxer.hikari.util.DriverDataSource; + +public class JdbcDriverTest +{ + private HikariDataSource ds; + + @After + public void teardown() + { + if (ds != null) { + ds.close(); + } + } + + @Test + public void driverTest1() throws SQLException + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(1); + config.setMaximumPoolSize(1); + config.setConnectionTestQuery("VALUES 1"); + config.setDriverClassName("com.zaxxer.hikari.mocks.StubDriver"); + config.setJdbcUrl("jdbc:stub"); + config.addDataSourceProperty("user", "bart"); + config.addDataSourceProperty("password", "simpson"); + + ds = new HikariDataSource(config); + + assertTrue(ds.isWrapperFor(DriverDataSource.class)); + + DriverDataSource unwrap = ds.unwrap(DriverDataSource.class); + assertNotNull(unwrap); + + try (Connection connection = ds.getConnection()) { + // test that getConnection() succeeds + } + } + + @Test + public void driverTest2() throws SQLException + { + HikariConfig config = newHikariConfig(); + + config.setMinimumIdle(1); + config.setMaximumPoolSize(1); + config.setConnectionTestQuery("VALUES 1"); + config.setDriverClassName("com.zaxxer.hikari.mocks.StubDriver"); + config.setJdbcUrl("jdbc:invalid"); + + try { + ds = new HikariDataSource(config); + } + catch (RuntimeException e) { + assertTrue(e.getMessage().contains("claims to not accept")); + } + } +} diff --git a/src/test/java/com/zaxxer/hikari/pool/MiscTest.java b/src/test/java/com/zaxxer/hikari/pool/MiscTest.java new file mode 100644 index 0000000..f9698ef --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/pool/MiscTest.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2013 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.zaxxer.hikari.pool; + +import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; +import static com.zaxxer.hikari.pool.TestElf.getPool; +import static com.zaxxer.hikari.pool.TestElf.setConfigUnitTest; +import static com.zaxxer.hikari.pool.TestElf.setSlf4jLogLevel; +import static com.zaxxer.hikari.pool.TestElf.setSlf4jTargetStream; +import static com.zaxxer.hikari.util.UtilityElf.createInstance; +import static com.zaxxer.hikari.util.UtilityElf.getTransactionIsolation; +import static com.zaxxer.hikari.util.UtilityElf.quietlySleep; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.apache.logging.log4j.Level; +import org.junit.Test; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; + +/** + * @author Brett Wooldridge + */ +public class MiscTest +{ + @Test + public void testLogWriter() throws SQLException + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(0); + config.setMaximumPoolSize(4); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + setConfigUnitTest(true); + + try (HikariDataSource ds = new HikariDataSource(config)) { + PrintWriter writer = new PrintWriter(System.out); + ds.setLogWriter(writer); + assertSame(writer, ds.getLogWriter()); + assertEquals("testLogWriter", config.getPoolName()); + } + finally + { + setConfigUnitTest(false); + } + } + + @Test + public void testInvalidIsolation() + { + try { + getTransactionIsolation("INVALID"); + fail(); + } + catch (Exception e) { + assertTrue(e instanceof IllegalArgumentException); + } + } + + @Test + public void testCreateInstance() + { + try { + createInstance("invalid", null); + fail(); + } + catch (RuntimeException e) { + assertTrue(e.getCause() instanceof ClassNotFoundException); + } + } + + @Test + public void testLeakDetection() throws Exception + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (PrintStream ps = new PrintStream(baos, true)) { + setSlf4jTargetStream(Class.forName("com.zaxxer.hikari.pool.ProxyLeakTask"), ps); + setConfigUnitTest(true); + + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(0); + config.setMaximumPoolSize(4); + config.setThreadFactory(Executors.defaultThreadFactory()); + config.setMetricRegistry(null); + config.setLeakDetectionThreshold(TimeUnit.SECONDS.toMillis(1)); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + setSlf4jLogLevel(HikariPool.class, Level.DEBUG); + getPool(ds).logPoolState(); + + try (Connection connection = ds.getConnection()) { + quietlySleep(SECONDS.toMillis(4)); + connection.close(); + quietlySleep(SECONDS.toMillis(1)); + ps.close(); + String s = new String(baos.toByteArray()); + assertNotNull("Exception string was null", s); + assertTrue("Expected exception to contain 'Connection leak detection' but contains *" + s + "*", s.contains("Connection leak detection")); + } + } + finally + { + setConfigUnitTest(false); + } + } + } +} diff --git a/src/test/java/com/zaxxer/hikari/pool/PostgresTest.java b/src/test/java/com/zaxxer/hikari/pool/PostgresTest.java new file mode 100644 index 0000000..693e7b3 --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/pool/PostgresTest.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2013, 2014 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.zaxxer.hikari.pool; + +import static com.zaxxer.hikari.pool.TestElf.getPool; +import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; +import static com.zaxxer.hikari.util.ClockSource.currentTime; +import static com.zaxxer.hikari.util.ClockSource.elapsedMillis; +import static com.zaxxer.hikari.util.UtilityElf.quietlySleep; +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.junit.Before; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import com.zaxxer.hikari.util.UtilityElf; + +/** + * This test is meant to be run manually and interactively and was + * build for issue #159. + * + * @author Brett Wooldridge + */ +public class PostgresTest +{ + //@Test + public void testCase1() throws Exception + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(3); + config.setMaximumPoolSize(10); + config.setConnectionTimeout(3000); + config.setIdleTimeout(TimeUnit.SECONDS.toMillis(10)); + config.setValidationTimeout(TimeUnit.SECONDS.toMillis(2)); + + config.setJdbcUrl("jdbc:pgsql://localhost:5432/test"); + config.setUsername("brettw"); + + try (final HikariDataSource ds = new HikariDataSource(config)) { + final long start = currentTime(); + do { + Thread t = new Thread() { + public void run() { + try (Connection connection = ds.getConnection()) { + System.err.println("Obtained connection " + connection); + quietlySleep(TimeUnit.SECONDS.toMillis((long)(10 + (Math.random() * 20)))); + } + catch (SQLException e) { + e.printStackTrace(); + } + } + }; + t.setDaemon(true); + t.start(); + + quietlySleep(TimeUnit.SECONDS.toMillis((long)((Math.random() * 20)))); + } while (elapsedMillis(start) < MINUTES.toMillis(15)); + } + } + + //@Test + public void testCase2() throws Exception + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(3); + config.setMaximumPoolSize(10); + config.setConnectionTimeout(1000); + config.setIdleTimeout(TimeUnit.SECONDS.toMillis(60)); + + config.setJdbcUrl("jdbc:pgsql://localhost:5432/test"); + config.setUsername("brettw"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + + try (Connection conn = ds.getConnection()) { + System.err.println("\nGot a connection, and released it. Now, enable the firewall."); + } + + getPool(ds).logPoolState(); + quietlySleep(5000L); + + System.err.println("\nNow attempting another getConnection(), expecting a timeout..."); + + long start = currentTime(); + try (Connection conn = ds.getConnection()) { + System.err.println("\nOpps, got a connection. Did you enable the firewall? " + conn); + fail("Opps, got a connection. Did you enable the firewall?"); + } + catch (SQLException e) + { + assertTrue("Timeout less than expected " + elapsedMillis(start) + "ms", elapsedMillis(start) > 5000); + } + + System.err.println("\nOk, so far so good. Now, disable the firewall again. Attempting connection in 5 seconds..."); + quietlySleep(5000L); + getPool(ds).logPoolState(); + + try (Connection conn = ds.getConnection()) { + System.err.println("\nGot a connection, and released it."); + } + } + + System.err.println("\nPassed."); + } + + //@Test + public void testCase3() throws Exception + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(3); + config.setMaximumPoolSize(10); + config.setConnectionTimeout(1000); + config.setIdleTimeout(TimeUnit.SECONDS.toMillis(60)); + + config.setJdbcUrl("jdbc:pgsql://localhost:5432/test"); + config.setUsername("brettw"); + + try (final HikariDataSource ds = new HikariDataSource(config)) { + for (int i = 0; i < 10; i++) { + new Thread() { + public void run() { + try (Connection conn = ds.getConnection()) { + System.err.println("ERROR: should not have acquired connection."); + } + catch (SQLException e) { + // expected + } + } + }.start(); + } + + quietlySleep(5000L); + + System.err.println("Now, bring the DB online. Checking pool in 15 seconds."); + quietlySleep(15000L); + + getPool(ds).logPoolState(); + } + } + + // @Test + public void testCase4() throws Exception + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(0); + config.setMaximumPoolSize(15); + config.setConnectionTimeout(10000); + config.setIdleTimeout(TimeUnit.MINUTES.toMillis(1)); + config.setMaxLifetime(TimeUnit.MINUTES.toMillis(2)); + config.setRegisterMbeans(true); + + config.setJdbcUrl("jdbc:postgresql://localhost:5432/netld"); + config.setUsername("brettw"); + + try (final HikariDataSource ds = new HikariDataSource(config)) { + + countdown(20); + List<Thread> threads = new ArrayList<>(); + for (int i = 0; i < 20; i++) { + threads.add(new Thread() { + public void run() { + UtilityElf.quietlySleep((long)(Math.random() * 2500L)); + final long start = currentTime(); + do { + try (Connection conn = ds.getConnection(); Statement stmt = conn.createStatement()) { + try (ResultSet rs = stmt.executeQuery("SELECT * FROM device WHERE device_id=0 ORDER BY device_id LIMIT 1 OFFSET 0")) { + rs.next(); + } + UtilityElf.quietlySleep(100L); //Math.max(50L, (long)(Math.random() * 250L))); + } + catch (SQLException e) { + e.printStackTrace(); + // throw new RuntimeException(e); + } + + // UtilityElf.quietlySleep(10L); //Math.max(50L, (long)(Math.random() * 250L))); + } while (elapsedMillis(start) < TimeUnit.MINUTES.toMillis(5)); + } + }); + } + +// threads.forEach(t -> t.start()); +// threads.forEach(t -> { try { t.join(); } catch (InterruptedException e) {} }); + } + } + + @Before + public void before() + { + System.err.println("\n"); + } + + private void countdown(int seconds) + { + do { + System.out.printf("Starting in %d seconds...\n", seconds); + if (seconds > 10) { + UtilityElf.quietlySleep(TimeUnit.SECONDS.toMillis(10)); + seconds -= 10; + } + else { + UtilityElf.quietlySleep(TimeUnit.SECONDS.toMillis(1)); + seconds -= 1; + } + } while (seconds > 0); + } +} diff --git a/src/test/java/com/zaxxer/hikari/pool/RampUpDown.java b/src/test/java/com/zaxxer/hikari/pool/RampUpDown.java new file mode 100755 index 0000000..e09894d --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/pool/RampUpDown.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2013, 2014 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package com.zaxxer.hikari.pool; + +import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; +import static com.zaxxer.hikari.pool.TestElf.getPool; +import static com.zaxxer.hikari.util.UtilityElf.quietlySleep; +import static org.junit.Assert.assertSame; + +import java.sql.Connection; +import java.sql.SQLException; + +import org.junit.Assert; +import org.junit.Test; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; + +public class RampUpDown +{ + @Test + public void rampUpDownTest() throws SQLException + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(5); + config.setMaximumPoolSize(60); + config.setInitializationFailTimeout(0); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "250"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + + ds.setIdleTimeout(1000); + HikariPool pool = getPool(ds); + + // wait two housekeeping periods so we don't fail if this part of test runs too quickly + quietlySleep(500); + + Assert.assertSame("Total connections not as expected", 5, pool.getTotalConnections()); + + Connection[] connections = new Connection[ds.getMaximumPoolSize()]; + for (int i = 0; i < connections.length; i++) + { + connections[i] = ds.getConnection(); + } + + assertSame("Total connections not as expected", 60, pool.getTotalConnections()); + + for (Connection connection : connections) + { + connection.close(); + } + + quietlySleep(500); + + assertSame("Total connections not as expected", 5, pool.getTotalConnections()); + } + } +} diff --git a/src/test/java/com/zaxxer/hikari/pool/ShutdownTest.java b/src/test/java/com/zaxxer/hikari/pool/ShutdownTest.java new file mode 100644 index 0000000..aa12a43 --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/pool/ShutdownTest.java @@ -0,0 +1,356 @@ +/* + * Copyright (C) 2014 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.zaxxer.hikari.pool; + +import static com.zaxxer.hikari.pool.TestElf.getPool; +import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; +import static com.zaxxer.hikari.pool.TestElf.setSlf4jLogLevel; +import static com.zaxxer.hikari.util.ClockSource.currentTime; +import static com.zaxxer.hikari.util.ClockSource.elapsedMillis; +import static com.zaxxer.hikari.util.UtilityElf.quietlySleep; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import org.apache.logging.log4j.Level; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import com.zaxxer.hikari.mocks.StubConnection; +import com.zaxxer.hikari.util.UtilityElf; + +/** + * @author Brett Wooldridge + */ +public class ShutdownTest +{ + @Before + public void beforeTest() + { + setSlf4jLogLevel(PoolBase.class, Level.DEBUG); + setSlf4jLogLevel(HikariPool.class, Level.DEBUG); + StubConnection.count.set(0); + } + + @After + public void afterTest() + { + setSlf4jLogLevel(PoolBase.class, Level.WARN); + setSlf4jLogLevel(HikariPool.class, Level.WARN); + StubConnection.slowCreate = false; + } + + @Test + public void testShutdown1() throws SQLException + { + Assert.assertSame("StubConnection count not as expected", 0, StubConnection.count.get()); + + StubConnection.slowCreate = true; + + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(0); + config.setMaximumPoolSize(10); + config.setInitializationFailTimeout(Long.MAX_VALUE); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + HikariPool pool = getPool(ds); + + Thread[] threads = new Thread[10]; + for (int i = 0; i < 10; i++) { + threads[i] = new Thread() { + @Override + public void run() + { + try { + if (ds.getConnection() != null) { + quietlySleep(SECONDS.toMillis(1)); + } + } + catch (SQLException e) { + } + } + }; + threads[i].setDaemon(true); + } + for (int i = 0; i < 10; i++) { + threads[i].start(); + } + + quietlySleep(1800L); + + assertTrue("Total connection count not as expected, ", pool.getTotalConnections() > 0); + + ds.close(); + + assertSame("Active connection count not as expected, ", 0, pool.getActiveConnections()); + assertSame("Idle connection count not as expected, ", 0, pool.getIdleConnections()); + assertSame("Total connection count not as expected, ", 0, pool.getTotalConnections()); + assertTrue(ds.isClosed()); + } + } + + @Test + public void testShutdown2() throws SQLException + { + assertSame("StubConnection count not as expected", 0, StubConnection.count.get()); + + StubConnection.slowCreate = true; + + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(10); + config.setMaximumPoolSize(10); + config.setInitializationFailTimeout(0); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + HikariPool pool = getPool(ds); + + quietlySleep(1200L); + + assertTrue("Total connection count not as expected, ", pool.getTotalConnections() > 0); + + ds.close(); + + assertSame("Active connection count not as expected, ", 0, pool.getActiveConnections()); + assertSame("Idle connection count not as expected, ", 0, pool.getIdleConnections()); + assertSame("Total connection count not as expected, ", 0, pool.getTotalConnections()); + assertTrue(ds.toString().startsWith("HikariDataSource (") && ds.toString().endsWith(")")); + } + } + + @Test + public void testShutdown3() throws SQLException + { + assertSame("StubConnection count not as expected", 0, StubConnection.count.get()); + + StubConnection.slowCreate = false; + + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(5); + config.setMaximumPoolSize(5); + config.setInitializationFailTimeout(0); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + HikariPool pool = getPool(ds); + + quietlySleep(1200L); + + assertTrue("Total connection count not as expected, ", pool.getTotalConnections() == 5); + + ds.close(); + + assertSame("Active connection count not as expected, ", 0, pool.getActiveConnections()); + assertSame("Idle connection count not as expected, ", 0, pool.getIdleConnections()); + assertSame("Total connection count not as expected, ", 0, pool.getTotalConnections()); + } + } + + @Test + public void testShutdown4() throws SQLException + { + StubConnection.slowCreate = true; + + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(10); + config.setMaximumPoolSize(10); + config.setInitializationFailTimeout(Long.MAX_VALUE); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + quietlySleep(500L); + + ds.close(); + + long startTime = currentTime(); + while (elapsedMillis(startTime) < SECONDS.toMillis(5) && threadCount() > 0) { + quietlySleep(250); + } + + assertSame("Unreleased connections after shutdown", 0, getPool(ds).getTotalConnections()); + } + } + + @Test + public void testShutdown5() throws SQLException + { + Assert.assertSame("StubConnection count not as expected", 0, StubConnection.count.get()); + + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(5); + config.setMaximumPoolSize(5); + config.setInitializationFailTimeout(0); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + HikariPool pool = getPool(ds); + + Connection[] connections = new Connection[5]; + for (int i = 0; i < 5; i++) { + connections[i] = ds.getConnection(); + } + + Assert.assertTrue("Total connection count not as expected, ", pool.getTotalConnections() == 5); + + ds.close(); + + Assert.assertSame("Active connection count not as expected, ", 0, pool.getActiveConnections()); + Assert.assertSame("Idle connection count not as expected, ", 0, pool.getIdleConnections()); + Assert.assertSame("Total connection count not as expected, ", 0, pool.getTotalConnections()); + } + } + + @Test + public void testAfterShutdown() throws Exception + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(0); + config.setMaximumPoolSize(5); + config.setInitializationFailTimeout(0); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + ds.close(); + try { + ds.getConnection(); + } + catch (SQLException e) { + Assert.assertTrue(e.getMessage().contains("has been closed.")); + } + } + } + + @Test + public void testShutdownDuringInit() throws Exception + { + final HikariConfig config = newHikariConfig(); + config.setMinimumIdle(5); + config.setMaximumPoolSize(5); + config.setConnectionTimeout(1000); + config.setValidationTimeout(1000); + config.setInitializationFailTimeout(0); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + StubConnection.slowCreate = true; + UtilityElf.quietlySleep(3000L); + } + } + + @Test + public void testThreadedShutdown() throws Exception + { + final HikariConfig config = newHikariConfig(); + config.setMinimumIdle(5); + config.setMaximumPoolSize(5); + config.setConnectionTimeout(1000); + config.setValidationTimeout(1000); + config.setInitializationFailTimeout(0); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + for (int i = 0; i < 4; i++) { + try (final HikariDataSource ds = new HikariDataSource(config)) { + Thread t = new Thread() { + @Override + public void run() + { + try (Connection connection = ds.getConnection()) { + for (int i = 0; i < 10; i++) { + Connection connection2 = null; + try { + connection2 = ds.getConnection(); + PreparedStatement stmt = connection2.prepareStatement("SOMETHING"); + UtilityElf.quietlySleep(20); + stmt.getMaxFieldSize(); + } + catch (SQLException e) { + try { + if (connection2 != null) { + connection2.close(); + } + } + catch (SQLException e2) { + if (e2.getMessage().contains("shutdown") || e2.getMessage().contains("evicted")) { + break; + } + } + } + } + } + catch (Exception e) { + Assert.fail(e.getMessage()); + } + finally { + ds.close(); + } + } + }; + t.start(); + + Thread t2 = new Thread() { + @Override + public void run() + { + UtilityElf.quietlySleep(100); + try { + ds.close(); + } + catch (IllegalStateException e) { + Assert.fail(e.getMessage()); + } + } + }; + t2.start(); + + t.join(); + t2.join(); + + ds.close(); + } + } + } + + private int threadCount() + { + Thread[] threads = new Thread[Thread.activeCount() * 2]; + Thread.enumerate(threads); + + int count = 0; + for (Thread thread : threads) { + count += (thread != null && thread.getName().startsWith("Hikari")) ? 1 : 0; + } + + return count; + } +} diff --git a/src/test/java/com/zaxxer/hikari/pool/StatementTest.java b/src/test/java/com/zaxxer/hikari/pool/StatementTest.java new file mode 100644 index 0000000..ebe9c90 --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/pool/StatementTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2013, 2014 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package com.zaxxer.hikari.pool; + +import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; +import static com.zaxxer.hikari.pool.TestElf.getPool; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; + +public class StatementTest +{ + private HikariDataSource ds; + + @Before + public void setup() + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(1); + config.setMaximumPoolSize(2); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + ds = new HikariDataSource(config); + } + + @After + public void teardown() + { + ds.close(); + } + + @Test + public void testStatementClose() throws SQLException + { + ds.getConnection().close(); + + HikariPool pool = getPool(ds); + assertTrue("Total connections not as expected", pool.getTotalConnections() >= 1); + assertTrue("Idle connections not as expected", pool.getIdleConnections() >= 1); + + try (Connection connection = ds.getConnection()) { + assertNotNull(connection); + + assertTrue("Total connections not as expected", pool.getTotalConnections() >= 1); + assertTrue("Idle connections not as expected", pool.getIdleConnections() >= 0); + + Statement statement = connection.createStatement(); + assertNotNull(statement); + + connection.close(); + + assertTrue(statement.isClosed()); + } + } + + @Test + public void testAutoStatementClose() throws SQLException + { + try (Connection connection = ds.getConnection()) { + assertNotNull(connection); + + Statement statement1 = connection.createStatement(); + assertNotNull(statement1); + Statement statement2 = connection.createStatement(); + assertNotNull(statement2); + + connection.close(); + + assertTrue(statement1.isClosed()); + assertTrue(statement2.isClosed()); + } + } + + @Test + public void testDoubleStatementClose() throws SQLException + { + try (Connection connection = ds.getConnection(); + Statement statement1 = connection.createStatement()) { + statement1.close(); + statement1.close(); + } + } + + @Test + public void testOutOfOrderStatementClose() throws SQLException + { + try (Connection connection = ds.getConnection(); + Statement statement1 = connection.createStatement(); + Statement statement2 = connection.createStatement()) { + statement1.close(); + statement2.close(); + } + } +} diff --git a/src/test/java/com/zaxxer/hikari/pool/TestConcurrentBag.java b/src/test/java/com/zaxxer/hikari/pool/TestConcurrentBag.java new file mode 100644 index 0000000..b8d9d22 --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/pool/TestConcurrentBag.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2013 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.zaxxer.hikari.pool; + +import static com.zaxxer.hikari.pool.TestElf.getPool; +import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; +import static com.zaxxer.hikari.pool.TestElf.setSlf4jTargetStream; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.concurrent.CompletableFuture; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import com.zaxxer.hikari.util.ConcurrentBag; + +/** + * + * @author Brett Wooldridge + */ +public class TestConcurrentBag +{ + private static HikariDataSource ds; + private static HikariPool pool; + + @BeforeClass + public static void setup() + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(1); + config.setMaximumPoolSize(2); + config.setInitializationFailTimeout(0); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + ds = new HikariDataSource(config); + pool = getPool(ds); + } + + @AfterClass + public static void teardown() + { + ds.close(); + } + + @Test + public void testConcurrentBag() throws Exception + { + try (ConcurrentBag<PoolEntry> bag = new ConcurrentBag<>((x) -> CompletableFuture.completedFuture(Boolean.TRUE))) { + assertEquals(0, bag.values(8).size()); + + PoolEntry reserved = pool.newPoolEntry(); + bag.add(reserved); + bag.reserve(reserved); // reserved + + PoolEntry inuse = pool.newPoolEntry(); + bag.add(inuse); + bag.borrow(2, MILLISECONDS); // in use + + PoolEntry notinuse = pool.newPoolEntry(); + bag.add(notinuse); // not in use + + bag.dumpState(); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream(baos, true); + setSlf4jTargetStream(ConcurrentBag.class, ps); + + bag.requite(reserved); + + bag.remove(notinuse); + assertTrue(new String(baos.toByteArray()).contains("not borrowed or reserved")); + + bag.unreserve(notinuse); + assertTrue(new String(baos.toByteArray()).contains("was not reserved")); + + bag.remove(inuse); + bag.remove(inuse); + assertTrue(new String(baos.toByteArray()).contains("not borrowed or reserved")); + + bag.close(); + try { + PoolEntry bagEntry = pool.newPoolEntry(); + bag.add(bagEntry); + assertNotEquals(bagEntry, bag.borrow(100, MILLISECONDS)); + } + catch (IllegalStateException e) { + assertTrue(new String(baos.toByteArray()).contains("ignoring add()")); + } + + assertNotNull(notinuse.toString()); + } + } +} diff --git a/src/test/java/com/zaxxer/hikari/pool/TestConnectionCloseBlocking.java b/src/test/java/com/zaxxer/hikari/pool/TestConnectionCloseBlocking.java new file mode 100644 index 0000000..8d98f83 --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/pool/TestConnectionCloseBlocking.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2013, 2014 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * + */ +package com.zaxxer.hikari.pool; + +import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; +import static com.zaxxer.hikari.util.ClockSource.currentTime; +import static com.zaxxer.hikari.util.ClockSource.elapsedMillis; +import static com.zaxxer.hikari.util.UtilityElf.quietlySleep; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.when; + +import java.sql.Connection; +import java.sql.SQLException; + +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import com.zaxxer.hikari.mocks.MockDataSource; + +/** + * Test for cases when db network connectivity goes down and close is called on existing connections. By default Hikari + * blocks longer than getMaximumTimeout (it can hang for a lot of time depending on driver timeout settings). Closing + * async the connections fixes this issue. + * + */ +public class TestConnectionCloseBlocking { + private static volatile boolean shouldFail = false; + + // @Test + public void testConnectionCloseBlocking() throws SQLException { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(0); + config.setMaximumPoolSize(1); + config.setConnectionTimeout(1500); + config.setDataSource(new CustomMockDataSource()); + + long start = currentTime(); + try (HikariDataSource ds = new HikariDataSource(config); + Connection connection = ds.getConnection()) { + + connection.close(); + + // Hikari only checks for validity for connections with lastAccess > 1000 ms so we sleep for 1001 ms to force + // Hikari to do a connection validation which will fail and will trigger the connection to be closed + quietlySleep(1100L); + + shouldFail = true; + + // on physical connection close we sleep 2 seconds + try (Connection connection2 = ds.getConnection()) { + assertTrue("Waited longer than timeout", (elapsedMillis(start) < config.getConnectionTimeout())); + } + } catch (SQLException e) { + assertTrue("getConnection failed because close connection took longer than timeout", (elapsedMillis(start) < config.getConnectionTimeout())); + } + } + + private static class CustomMockDataSource extends MockDataSource { + @Override + public Connection getConnection() throws SQLException { + Connection mockConnection = super.getConnection(); + when(mockConnection.isValid(anyInt())).thenReturn(!shouldFail); + doAnswer(new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + if (shouldFail) { + SECONDS.sleep(2); + } + return null; + } + }).when(mockConnection).close(); + return mockConnection; + } + } + +} diff --git a/src/test/java/com/zaxxer/hikari/pool/TestConnectionTimeoutRetry.java b/src/test/java/com/zaxxer/hikari/pool/TestConnectionTimeoutRetry.java new file mode 100644 index 0000000..15f8974 --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/pool/TestConnectionTimeoutRetry.java @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2013, 2014 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package com.zaxxer.hikari.pool; + +import static com.zaxxer.hikari.pool.TestElf.getPool; +import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; +import static com.zaxxer.hikari.pool.TestElf.setSlf4jLogLevel; +import static com.zaxxer.hikari.pool.TestElf.setSlf4jTargetStream; +import static com.zaxxer.hikari.util.ClockSource.currentTime; +import static com.zaxxer.hikari.util.ClockSource.elapsedMillis; +import static java.lang.Thread.sleep; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.apache.logging.log4j.Level; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import com.zaxxer.hikari.mocks.StubConnection; +import com.zaxxer.hikari.mocks.StubDataSource; + +public class TestConnectionTimeoutRetry +{ + @Test + public void testConnectionRetries() throws SQLException + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(0); + config.setMaximumPoolSize(1); + config.setConnectionTimeout(2800); + config.setValidationTimeout(2800); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + StubDataSource stubDataSource = ds.unwrap(StubDataSource.class); + stubDataSource.setThrowException(new SQLException("Connection refused")); + + long start = currentTime(); + try (Connection connection = ds.getConnection()) { + connection.close(); + fail("Should not have been able to get a connection."); + } + catch (SQLException e) { + long elapsed = elapsedMillis(start); + long timeout = config.getConnectionTimeout(); + assertTrue("Didn't wait long enough for timeout", (elapsed >= timeout)); + } + } + } + + @Test + public void testConnectionRetries2() throws SQLException + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(0); + config.setMaximumPoolSize(1); + config.setConnectionTimeout(2800); + config.setValidationTimeout(2800); + config.setInitializationFailTimeout(0); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + final StubDataSource stubDataSource = ds.unwrap(StubDataSource.class); + stubDataSource.setThrowException(new SQLException("Connection refused")); + + ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + scheduler.schedule(new Runnable() { + @Override + public void run() + { + stubDataSource.setThrowException(null); + } + }, 300, TimeUnit.MILLISECONDS); + + long start = currentTime(); + try { + try (Connection connection = ds.getConnection()) { + // close immediately + } + + long elapsed = elapsedMillis(start); + assertTrue("Connection returned too quickly, something is wrong.", elapsed > 250); + assertTrue("Waited too long to get a connection.", elapsed < config.getConnectionTimeout()); + } + catch (SQLException e) { + fail("Should not have timed out: " + e.getMessage()); + } + finally { + scheduler.shutdownNow(); + } + } + } + + @Test + public void testConnectionRetries3() throws SQLException + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(0); + config.setMaximumPoolSize(2); + config.setConnectionTimeout(2800); + config.setValidationTimeout(2800); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + final Connection connection1 = ds.getConnection(); + final Connection connection2 = ds.getConnection(); + assertNotNull(connection1); + assertNotNull(connection2); + + ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2); + scheduler.schedule(new Runnable() { + @Override + public void run() + { + try { + connection1.close(); + } + catch (Exception e) { + e.printStackTrace(System.err); + } + } + }, 800, MILLISECONDS); + + long start = currentTime(); + try { + try (Connection connection3 = ds.getConnection()) { + // close immediately + } + + long elapsed = elapsedMillis(start); + assertTrue("Waited too long to get a connection.", (elapsed >= 700) && (elapsed < 950)); + } + catch (SQLException e) { + fail("Should not have timed out."); + } + finally { + scheduler.shutdownNow(); + } + } + } + + @Test + public void testConnectionRetries5() throws SQLException + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(0); + config.setMaximumPoolSize(2); + config.setConnectionTimeout(1000); + config.setValidationTimeout(1000); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + final Connection connection1 = ds.getConnection(); + + long start = currentTime(); + + ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2); + scheduler.schedule(new Runnable() { + @Override + public void run() + { + try { + connection1.close(); + } + catch (Exception e) { + e.printStackTrace(System.err); + } + } + }, 250, MILLISECONDS); + + StubDataSource stubDataSource = ds.unwrap(StubDataSource.class); + stubDataSource.setThrowException(new SQLException("Connection refused")); + + try { + try (Connection connection2 = ds.getConnection()) { + // close immediately + } + + long elapsed = elapsedMillis(start); + assertTrue("Waited too long to get a connection.", (elapsed >= 250) && (elapsed < config.getConnectionTimeout())); + } + catch (SQLException e) { + fail("Should not have timed out."); + } + finally { + scheduler.shutdownNow(); + } + } + } + + @Test + public void testConnectionIdleFill() throws Exception + { + StubConnection.slowCreate = false; + + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(5); + config.setMaximumPoolSize(10); + config.setConnectionTimeout(2000); + config.setValidationTimeout(2000); + config.setConnectionTestQuery("VALUES 2"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "400"); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream(baos, true); + setSlf4jTargetStream(HikariPool.class, ps); + + try (HikariDataSource ds = new HikariDataSource(config)) { + setSlf4jLogLevel(HikariPool.class, Level.DEBUG); + + HikariPool pool = getPool(ds); + try ( + Connection connection1 = ds.getConnection(); + Connection connection2 = ds.getConnection(); + Connection connection3 = ds.getConnection(); + Connection connection4 = ds.getConnection(); + Connection connection5 = ds.getConnection(); + Connection connection6 = ds.getConnection(); + Connection connection7 = ds.getConnection()) { + + sleep(1300); + + assertSame("Total connections not as expected", 10, pool.getTotalConnections()); + assertSame("Idle connections not as expected", 3, pool.getIdleConnections()); + } + + assertSame("Total connections not as expected", 10, pool.getTotalConnections()); + assertSame("Idle connections not as expected", 10, pool.getIdleConnections()); + } + } + + @Before + public void before() + { + setSlf4jLogLevel(HikariPool.class, Level.INFO); + } + + @After + public void after() + { + System.getProperties().remove("com.zaxxer.hikari.housekeeping.periodMs"); + } +} diff --git a/src/test/java/com/zaxxer/hikari/pool/TestConnections.java b/src/test/java/com/zaxxer/hikari/pool/TestConnections.java new file mode 100644 index 0000000..538d9ac --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/pool/TestConnections.java @@ -0,0 +1,621 @@ +/* + * Copyright (C) 2013 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.zaxxer.hikari.pool; + +import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; +import static com.zaxxer.hikari.pool.TestElf.newHikariDataSource; +import static com.zaxxer.hikari.pool.TestElf.getPool; +import static com.zaxxer.hikari.pool.TestElf.setConfigUnitTest; +import static com.zaxxer.hikari.pool.TestElf.setSlf4jLogLevel; +import static com.zaxxer.hikari.pool.TestElf.setSlf4jTargetStream; +import static com.zaxxer.hikari.util.UtilityElf.quietlySleep; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLTransientConnectionException; +import java.sql.Statement; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.logging.log4j.Level; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import com.zaxxer.hikari.mocks.StubConnection; +import com.zaxxer.hikari.mocks.StubDataSource; +import com.zaxxer.hikari.mocks.StubStatement; +import com.zaxxer.hikari.pool.HikariPool.PoolInitializationException; + +/** + * @author Brett Wooldridge + */ +public class TestConnections +{ + @Before + public void before() + { + setSlf4jTargetStream(HikariPool.class, System.err); + setSlf4jLogLevel(HikariPool.class, Level.DEBUG); + setSlf4jLogLevel(PoolBase.class, Level.DEBUG); + } + + @After + public void after() + { + System.getProperties().remove("com.zaxxer.hikari.housekeeping.periodMs"); + setSlf4jLogLevel(HikariPool.class, Level.WARN); + setSlf4jLogLevel(PoolBase.class, Level.WARN); + } + + @Test + public void testCreate() throws SQLException + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(1); + config.setMaximumPoolSize(1); + config.setConnectionTestQuery("VALUES 1"); + config.setConnectionInitSql("SELECT 1"); + config.setReadOnly(true); + config.setConnectionTimeout(2500); + config.setLeakDetectionThreshold(TimeUnit.SECONDS.toMillis(30)); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + ds.setLoginTimeout(10); + assertSame(10, ds.getLoginTimeout()); + + HikariPool pool = getPool(ds); + ds.getConnection().close(); + assertSame("Total connections not as expected", 1, pool.getTotalConnections()); + assertSame("Idle connections not as expected", 1, pool.getIdleConnections()); + + try (Connection connection = ds.getConnection(); + PreparedStatement statement = connection.prepareStatement("SELECT * FROM device WHERE device_id=?")) { + + assertNotNull(connection); + assertNotNull(statement); + + assertSame("Total connections not as expected", 1, pool.getTotalConnections()); + assertSame("Idle connections not as expected", 0, pool.getIdleConnections()); + + statement.setInt(1, 0); + + try (ResultSet resultSet = statement.executeQuery()) { + assertNotNull(resultSet); + + assertFalse(resultSet.next()); + } + } + + assertSame("Total connections not as expected", 1, pool.getTotalConnections()); + assertSame("Idle connections not as expected", 1, pool.getIdleConnections()); + } + } + + @Test + public void testMaxLifetime() throws Exception + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(0); + config.setMaximumPoolSize(1); + config.setConnectionTimeout(2500); + config.setConnectionTestQuery("VALUES 1"); + config.setInitializationFailTimeout(Long.MAX_VALUE); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "100"); + + setConfigUnitTest(true); + try (HikariDataSource ds = new HikariDataSource(config)) { + System.clearProperty("com.zaxxer.hikari.housekeeping.periodMs"); + + ds.setMaxLifetime(700); + + HikariPool pool = getPool(ds); + + assertSame("Total connections not as expected", 0, pool.getTotalConnections()); + assertSame("Idle connections not as expected", 0, pool.getIdleConnections()); + + Connection unwrap; + Connection unwrap2; + try (Connection connection = ds.getConnection()) { + unwrap = connection.unwrap(Connection.class); + assertNotNull(connection); + + assertSame("Second total connections not as expected", 1, pool.getTotalConnections()); + assertSame("Second idle connections not as expected", 0, pool.getIdleConnections()); + } + + assertSame("Idle connections not as expected", 1, pool.getIdleConnections()); + + try (Connection connection = ds.getConnection()) { + unwrap2 = connection.unwrap(Connection.class); + assertSame(unwrap, unwrap2); + assertSame("Second total connections not as expected", 1, pool.getTotalConnections()); + assertSame("Second idle connections not as expected", 0, pool.getIdleConnections()); + } + + quietlySleep(TimeUnit.SECONDS.toMillis(2)); + + try (Connection connection = ds.getConnection()) { + unwrap2 = connection.unwrap(Connection.class); + assertNotSame("Expected a different connection", unwrap, unwrap2); + } + + assertSame("Post total connections not as expected", 1, pool.getTotalConnections()); + assertSame("Post idle connections not as expected", 1, pool.getIdleConnections()); + } + finally { + setConfigUnitTest(false); + } + } + + @Test + public void testMaxLifetime2() throws Exception + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(0); + config.setMaximumPoolSize(1); + config.setConnectionTimeout(2500); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "100"); + + setConfigUnitTest(true); + try (HikariDataSource ds = new HikariDataSource(config)) { + ds.setMaxLifetime(700); + + HikariPool pool = getPool(ds); + assertSame("Total connections not as expected", 0, pool.getTotalConnections()); + assertSame("Idle connections not as expected", 0, pool.getIdleConnections()); + + Connection unwrap; + Connection unwrap2; + try (Connection connection = ds.getConnection()) { + unwrap = connection.unwrap(Connection.class); + assertNotNull(connection); + + assertSame("Second total connections not as expected", 1, pool.getTotalConnections()); + assertSame("Second idle connections not as expected", 0, pool.getIdleConnections()); + } + + assertSame("Idle connections not as expected", 1, pool.getIdleConnections()); + + try (Connection connection = ds.getConnection()) { + unwrap2 = connection.unwrap(Connection.class); + assertSame(unwrap, unwrap2); + assertSame("Second total connections not as expected", 1, pool.getTotalConnections()); + assertSame("Second idle connections not as expected", 0, pool.getIdleConnections()); + } + + quietlySleep(800); + + try (Connection connection = ds.getConnection()) { + unwrap2 = connection.unwrap(Connection.class); + assertNotSame("Expected a different connection", unwrap, unwrap2); + } + + assertSame("Post total connections not as expected", 1, pool.getTotalConnections()); + assertSame("Post idle connections not as expected", 1, pool.getIdleConnections()); + } + finally { + setConfigUnitTest(false); + } + } + + @Test + public void testDoubleClose() throws Exception + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(1); + config.setMaximumPoolSize(1); + config.setConnectionTimeout(2500); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (HikariDataSource ds = new HikariDataSource(config); + Connection connection = ds.getConnection()) { + connection.close(); + + // should no-op + connection.abort(null); + + assertTrue("Connection should have closed", connection.isClosed()); + assertFalse("Connection should have closed", connection.isValid(5)); + assertTrue("Expected to contain ClosedConnection, but was " + connection, connection.toString().contains("ClosedConnection")); + } + } + + @Test + public void testEviction() throws Exception + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(0); + config.setMaximumPoolSize(5); + config.setConnectionTimeout(2500); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + Connection connection = ds.getConnection(); + + HikariPool pool = getPool(ds); + assertEquals(1, pool.getTotalConnections()); + ds.evictConnection(connection); + assertEquals(0, pool.getTotalConnections()); + } + } + + @Test + public void testBackfill() throws Exception + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(1); + config.setMaximumPoolSize(4); + config.setConnectionTimeout(1000); + config.setInitializationFailTimeout(Long.MAX_VALUE); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + StubConnection.slowCreate = true; + try (HikariDataSource ds = new HikariDataSource(config)) { + + HikariPool pool = getPool(ds); + quietlySleep(1250); + + assertSame("Total connections not as expected", 1, pool.getTotalConnections()); + assertSame("Idle connections not as expected", 1, pool.getIdleConnections()); + + // This will take the pool down to zero + try (Connection connection = ds.getConnection()) { + assertNotNull(connection); + + assertSame("Total connections not as expected", 1, pool.getTotalConnections()); + assertSame("Idle connections not as expected", 0, pool.getIdleConnections()); + + PreparedStatement statement = connection.prepareStatement("SELECT some, thing FROM somewhere WHERE something=?"); + assertNotNull(statement); + + ResultSet resultSet = statement.executeQuery(); + assertNotNull(resultSet); + + try { + statement.getMaxFieldSize(); + fail(); + } + catch (Exception e) { + assertSame(SQLException.class, e.getClass()); + } + + pool.logPoolState("testBackfill() before close..."); + + // The connection will be ejected from the pool here + } + + assertSame("Total connections not as expected", 0, pool.getTotalConnections()); + + pool.logPoolState("testBackfill() after close..."); + + quietlySleep(1250); + + assertSame("Total connections not as expected", 1, pool.getTotalConnections()); + assertSame("Idle connections not as expected", 1, pool.getIdleConnections()); + } + finally { + StubConnection.slowCreate = false; + } + } + + @Test + public void testMaximumPoolLimit() throws Exception + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(1); + config.setMaximumPoolSize(4); + config.setConnectionTimeout(20000); + config.setInitializationFailTimeout(0); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + final AtomicReference<Exception> ref = new AtomicReference<>(); + + StubConnection.count.set(0); // reset counter + + try (final HikariDataSource ds = new HikariDataSource(config)) { + + final HikariPool pool = getPool(ds); + + Thread[] threads = new Thread[20]; + for (int i = 0; i < threads.length; i++) { + threads[i] = new Thread(new Runnable() { + @Override + public void run() + { + try { + pool.logPoolState("Before acquire "); + try (Connection connection = ds.getConnection()) { + pool.logPoolState("After acquire "); + quietlySleep(500); + } + } + catch (Exception e) { + ref.set(e); + } + } + }); + } + + for (int i = 0; i < threads.length; i++) { + threads[i].start(); + } + + for (int i = 0; i < threads.length; i++) { + threads[i].join(); + } + + pool.logPoolState("before check "); + assertNull((ref.get() != null ? ref.get().toString() : ""), ref.get()); + assertSame("StubConnection count not as expected", 4, StubConnection.count.get()); + } + } + + @Test + public void testOldDriver() throws Exception + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(1); + config.setMaximumPoolSize(1); + config.setConnectionTimeout(2500); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + StubConnection.oldDriver = true; + StubStatement.oldDriver = true; + try (HikariDataSource ds = new HikariDataSource(config)) { + quietlySleep(500); + + try (Connection connection = ds.getConnection()) { + // close + } + + quietlySleep(500); + try (Connection connection = ds.getConnection()) { + // close + } + } + finally { + StubConnection.oldDriver = false; + StubStatement.oldDriver = false; + } + } + + @Test + public void testSuspendResume() throws Exception + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(3); + config.setMaximumPoolSize(3); + config.setConnectionTimeout(2500); + config.setAllowPoolSuspension(true); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (final HikariDataSource ds = new HikariDataSource(config)) { + HikariPool pool = getPool(ds); + while (pool.getTotalConnections() < 3) { + quietlySleep(50); + } + + Thread t = new Thread(new Runnable() { + @Override + public void run() + { + try { + ds.getConnection(); + ds.getConnection(); + } + catch (Exception e) { + fail(); + } + } + }); + + try (Connection c3 = ds.getConnection()) { + assertEquals(2, pool.getIdleConnections()); + + pool.suspendPool(); + t.start(); + + quietlySleep(500); + assertEquals(2, pool.getIdleConnections()); + } + assertEquals(3, pool.getIdleConnections()); + pool.resumePool(); + quietlySleep(500); + assertEquals(1, pool.getIdleConnections()); + } + } + + @Test + public void testInitializationFailure1() throws SQLException + { + StubDataSource stubDataSource = new StubDataSource(); + stubDataSource.setThrowException(new SQLException("Connection refused")); + + try (HikariDataSource ds = newHikariDataSource()) { + ds.setMinimumIdle(1); + ds.setMaximumPoolSize(1); + ds.setConnectionTimeout(2500); + ds.setConnectionTestQuery("VALUES 1"); + ds.setDataSource(stubDataSource); + + try (Connection c = ds.getConnection()) { + fail("Initialization should have failed"); + } + catch (SQLException e) { + // passed + } + } + } + + @Test + public void testInitializationFailure2() throws SQLException + { + StubDataSource stubDataSource = new StubDataSource(); + stubDataSource.setThrowException(new SQLException("Connection refused")); + + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(1); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSource(stubDataSource); + + try (HikariDataSource ds = new HikariDataSource(config); + Connection c = ds.getConnection()) { + fail("Initialization should have failed"); + } + catch (PoolInitializationException e) { + // passed + } + } + + @Test + public void testInvalidConnectionTestQuery() + { + class BadConnection extends StubConnection { + /** {@inheritDoc} */ + @Override + public Statement createStatement() throws SQLException + { + throw new SQLException("Simulated exception in createStatement()"); + } + } + + StubDataSource stubDataSource = new StubDataSource() { + /** {@inheritDoc} */ + @Override + public Connection getConnection() throws SQLException + { + return new BadConnection(); + } + }; + + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(1); + config.setMaximumPoolSize(2); + config.setConnectionTimeout(TimeUnit.SECONDS.toMillis(3)); + config.setConnectionTestQuery("VALUES 1"); + config.setInitializationFailTimeout(TimeUnit.SECONDS.toMillis(2)); + config.setDataSource(stubDataSource); + + try (HikariDataSource ds = new HikariDataSource(config)) { + try (Connection c = ds.getConnection()) { + fail("getConnection() should have failed"); + } + catch (SQLException e) { + assertSame("Simulated exception in createStatement()", e.getNextException().getMessage()); + } + } + catch (PoolInitializationException e) { + assertSame("Simulated exception in createStatement()", e.getCause().getMessage()); + } + + config.setInitializationFailTimeout(0); + try (HikariDataSource ds = new HikariDataSource(config)) { + fail("Initialization should have failed"); + } + catch (PoolInitializationException e) { + // passed + } + } + + @Test + public void testPopulationSlowAcquisition() throws InterruptedException, SQLException + { + HikariConfig config = newHikariConfig(); + config.setMaximumPoolSize(20); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "1000"); + + StubConnection.slowCreate = true; + try (HikariDataSource ds = new HikariDataSource(config)) { + System.clearProperty("com.zaxxer.hikari.housekeeping.periodMs"); + + ds.setIdleTimeout(3000); + + SECONDS.sleep(2); + + HikariPool pool = getPool(ds); + assertSame("Total connections not as expected", 2, pool.getTotalConnections()); + assertSame("Idle connections not as expected", 2, pool.getIdleConnections()); + + try (Connection connection = ds.getConnection()) { + assertNotNull(connection); + + SECONDS.sleep(20); + + assertSame("Second total connections not as expected", 20, pool.getTotalConnections()); + assertSame("Second idle connections not as expected", 19, pool.getIdleConnections()); + } + + assertSame("Idle connections not as expected", 20, pool.getIdleConnections()); + + SECONDS.sleep(5); + + assertSame("Third total connections not as expected", 20, pool.getTotalConnections()); + assertSame("Third idle connections not as expected", 20, pool.getIdleConnections()); + } + finally { + StubConnection.slowCreate = false; + } + } + + @Test + public void testMinimumIdleZero() throws SQLException + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(0); + config.setMaximumPoolSize(5); + config.setConnectionTimeout(1000L); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (HikariDataSource ds = new HikariDataSource(config); + Connection connection = ds.getConnection()) { + // passed + } + catch (SQLTransientConnectionException sqle) { + fail("Failed to obtain connection"); + } + } +} diff --git a/src/test/java/com/zaxxer/hikari/pool/TestElf.java b/src/test/java/com/zaxxer/hikari/pool/TestElf.java new file mode 100644 index 0000000..6438b11 --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/pool/TestElf.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2013 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.zaxxer.hikari.pool; + +import java.io.PrintStream; +import java.lang.reflect.Field; +import java.sql.Connection; +import java.util.HashMap; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.layout.CsvLogEventLayout; +import org.apache.logging.slf4j.Log4jLogger; +import org.slf4j.LoggerFactory; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; + +/** + * Utility methods for testing. + * + * @author Brett Wooldridge + */ +public final class TestElf +{ + private TestElf() { + // default constructor + } + + public static HikariPool getPool(HikariDataSource ds) + { + try { + Field field = ds.getClass().getDeclaredField("pool"); + field.setAccessible(true); + return (HikariPool) field.get(ds); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + @SuppressWarnings("unchecked") + public static HashMap<Object, HikariPool> getMultiPool(HikariDataSource ds) + { + try { + Field field = ds.getClass().getDeclaredField("multiPool"); + field.setAccessible(true); + return (HashMap<Object, HikariPool>) field.get(ds); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static boolean getConnectionCommitDirtyState(Connection connection) + { + try { + Field field = ProxyConnection.class.getDeclaredField("isCommitStateDirty"); + field.setAccessible(true); + return field.getBoolean(connection); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void setConfigUnitTest(boolean unitTest) + { + try { + Field field = HikariConfig.class.getDeclaredField("unitTest"); + field.setAccessible(true); + field.setBoolean(null, unitTest); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void setSlf4jTargetStream(Class<?> clazz, PrintStream stream) + { + try { + Log4jLogger log4Jlogger = (Log4jLogger) LoggerFactory.getLogger(clazz); + + Field field = clazz.getClassLoader().loadClass("org.apache.logging.slf4j.Log4jLogger").getDeclaredField("logger"); + field.setAccessible(true); + + Logger logger = (Logger) field.get(log4Jlogger); + if (logger.getAppenders().containsKey("string")) { + Appender appender = logger.getAppenders().get("string"); + logger.removeAppender(appender); + } + + logger.addAppender(new StringAppender("string", stream)); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void setSlf4jLogLevel(Class<?> clazz, Level logLevel) + { + try { + Log4jLogger log4Jlogger = (Log4jLogger) LoggerFactory.getLogger(clazz); + + Field field = clazz.getClassLoader().loadClass("org.apache.logging.slf4j.Log4jLogger").getDeclaredField("logger"); + field.setAccessible(true); + + Logger logger = (Logger) field.get(log4Jlogger); + logger.setLevel(logLevel); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static HikariConfig newHikariConfig() + { + final StackTraceElement callerStackTrace = Thread.currentThread().getStackTrace()[2]; + + String poolName = callerStackTrace.getMethodName(); + if ("setup".equals(poolName)) { + poolName = callerStackTrace.getClassName(); + } + + final HikariConfig config = new HikariConfig(); + config.setPoolName(poolName); + return config; + } + + public static HikariDataSource newHikariDataSource() + { + final StackTraceElement callerStackTrace = Thread.currentThread().getStackTrace()[2]; + + String poolName = callerStackTrace.getMethodName(); + if ("setup".equals(poolName)) { + poolName = callerStackTrace.getClassName(); + } + + final HikariDataSource ds = new HikariDataSource(); + ds.setPoolName(poolName); + return ds; + } + + private static class StringAppender extends AbstractAppender + { + private PrintStream stream; + + StringAppender(String name, PrintStream stream) + { + super(name, null, CsvLogEventLayout.createDefaultLayout()); + this.stream = stream; + } + + @Override + public void append(LogEvent event) + { + stream.println(event.getMessage().getFormattedMessage()); + } + } +} diff --git a/src/test/java/com/zaxxer/hikari/pool/TestHibernate.java b/src/test/java/com/zaxxer/hikari/pool/TestHibernate.java new file mode 100644 index 0000000..c1d6bae --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/pool/TestHibernate.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2013, 2014 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package com.zaxxer.hikari.pool; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import java.sql.Connection; +import java.util.Properties; + +import org.hibernate.service.UnknownUnwrapTypeException; +import org.junit.Test; + +import com.zaxxer.hikari.hibernate.HikariConnectionProvider; + +public class TestHibernate +{ + @Test + public void testConnectionProvider() throws Exception + { + HikariConnectionProvider provider = new HikariConnectionProvider(); + + Properties props = new Properties(); + props.load(getClass().getResourceAsStream("/hibernate.properties")); + + provider.configure(props); + Connection connection = provider.getConnection(); + provider.closeConnection(connection); + + assertNotNull(provider.unwrap(HikariConnectionProvider.class)); + assertFalse(provider.supportsAggressiveRelease()); + + try { + provider.unwrap(TestHibernate.class); + fail("Expected exception"); + } + catch (UnknownUnwrapTypeException e) { + } + + provider.stop(); + } +} diff --git a/src/test/java/com/zaxxer/hikari/pool/TestJNDI.java b/src/test/java/com/zaxxer/hikari/pool/TestJNDI.java new file mode 100644 index 0000000..b5cd40c --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/pool/TestJNDI.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2014 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zaxxer.hikari.pool; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.NamingException; +import javax.naming.RefAddr; +import javax.naming.Reference; + +import org.junit.Test; +import org.osjava.sj.jndi.AbstractContext; + +import com.zaxxer.hikari.HikariDataSource; +import com.zaxxer.hikari.HikariJNDIFactory; +import com.zaxxer.hikari.mocks.StubDataSource; + +public class TestJNDI +{ + @Test + public void testJndiLookup1() throws Exception + { + HikariJNDIFactory jndi = new HikariJNDIFactory(); + Reference ref = new Reference("javax.sql.DataSource"); + ref.add(new BogusRef("driverClassName", "com.zaxxer.hikari.mocks.StubDriver")); + ref.add(new BogusRef("jdbcUrl", "jdbc:stub")); + ref.add(new BogusRef("username", "foo")); + ref.add(new BogusRef("password", "foo")); + ref.add(new BogusRef("minimumIdle", "0")); + ref.add(new BogusRef("maxLifetime", "30000")); + ref.add(new BogusRef("maximumPoolSize", "10")); + ref.add(new BogusRef("dataSource.loginTimeout", "10")); + Context nameCtx = new BogusContext(); + + try (HikariDataSource ds = (HikariDataSource) jndi.getObjectInstance(ref, null, nameCtx, null)) { + assertNotNull(ds); + assertEquals("foo", ds.getUsername()); + } + } + + @Test + public void testJndiLookup2() throws Exception + { + HikariJNDIFactory jndi = new HikariJNDIFactory(); + Reference ref = new Reference("javax.sql.DataSource"); + ref.add(new BogusRef("dataSourceJNDI", "java:comp/env/HikariDS")); + ref.add(new BogusRef("driverClassName", "com.zaxxer.hikari.mocks.StubDriver")); + ref.add(new BogusRef("jdbcUrl", "jdbc:stub")); + ref.add(new BogusRef("username", "foo")); + ref.add(new BogusRef("password", "foo")); + ref.add(new BogusRef("minimumIdle", "0")); + ref.add(new BogusRef("maxLifetime", "30000")); + ref.add(new BogusRef("maximumPoolSize", "10")); + ref.add(new BogusRef("dataSource.loginTimeout", "10")); + Context nameCtx = new BogusContext2(); + + try (HikariDataSource ds = (HikariDataSource) jndi.getObjectInstance(ref, null, nameCtx, null)) { + assertNotNull(ds); + assertEquals("foo", ds.getUsername()); + } + } + + @Test + public void testJndiLookup3() throws Exception + { + HikariJNDIFactory jndi = new HikariJNDIFactory(); + + Reference ref = new Reference("javax.sql.DataSource"); + ref.add(new BogusRef("dataSourceJNDI", "java:comp/env/HikariDS")); + try { + jndi.getObjectInstance(ref, null, null, null); + fail(); + } + catch (RuntimeException e) { + assertTrue(e.getMessage().contains("JNDI context does not found")); + } + } + + private class BogusContext extends AbstractContext + { + @Override + public Context createSubcontext(Name name) throws NamingException + { + return null; + } + + @Override + public Object lookup(String name) throws NamingException + { + final HikariDataSource ds = new HikariDataSource(); + ds.setPoolName("TestJNDI"); + return ds; + } + } + + private class BogusContext2 extends AbstractContext + { + @Override + public Context createSubcontext(Name name) throws NamingException + { + return null; + } + + @Override + public Object lookup(String name) throws NamingException + { + return new StubDataSource(); + } + } + + private class BogusRef extends RefAddr + { + private static final long serialVersionUID = 1L; + + private String content; + BogusRef(String type, String content) + { + super(type); + this.content = content; + } + + @Override + public Object getContent() + { + return content; + } + } +} diff --git a/src/test/java/com/zaxxer/hikari/pool/TestMBean.java b/src/test/java/com/zaxxer/hikari/pool/TestMBean.java new file mode 100644 index 0000000..55ba733 --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/pool/TestMBean.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2013, 2014 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zaxxer.hikari.pool; + +import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; + +import java.sql.SQLException; + +import org.junit.Test; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; + +public class TestMBean +{ + @Test + public void testMBeanRegistration() throws SQLException + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(0); + config.setMaximumPoolSize(1); + config.setRegisterMbeans(true); + config.setConnectionTimeout(2800); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + // Close immediately + } + } +} diff --git a/src/test/java/com/zaxxer/hikari/pool/TestMetrics.java b/src/test/java/com/zaxxer/hikari/pool/TestMetrics.java new file mode 100644 index 0000000..fcba58e --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/pool/TestMetrics.java @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2013, 2014 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.zaxxer.hikari.pool; + +import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; +import static com.zaxxer.hikari.pool.TestElf.newHikariDataSource; +import static com.zaxxer.hikari.util.UtilityElf.quietlySleep; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeFalse; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.SortedMap; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import com.codahale.metrics.Histogram; +import com.codahale.metrics.Metric; +import com.codahale.metrics.MetricFilter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Timer; +import com.codahale.metrics.health.HealthCheck.Result; +import com.codahale.metrics.health.HealthCheckRegistry; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import com.zaxxer.hikari.metrics.MetricsTrackerFactory; +import com.zaxxer.hikari.metrics.dropwizard.CodahaleMetricsTrackerFactory; +import com.zaxxer.hikari.util.UtilityElf; + +import shaded.org.codehaus.plexus.interpolation.os.Os; + +/** + * Test HikariCP/CodaHale metrics integration. + * + * @author Brett Wooldridge + */ +public class TestMetrics +{ + @Test + public void testMetricWait() throws SQLException + { + MetricRegistry metricRegistry = new MetricRegistry(); + + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(1); + config.setMaximumPoolSize(1); + config.setMetricRegistry(metricRegistry); + config.setInitializationFailTimeout(Long.MAX_VALUE); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + ds.getConnection().close(); + + Timer timer = metricRegistry.getTimers(new MetricFilter() { + /** {@inheritDoc} */ + @Override + public boolean matches(String name, Metric metric) + { + return "testMetricWait.pool.Wait".equals(MetricRegistry.name("testMetricWait", "pool", "Wait")); + } + }).values().iterator().next(); + + assertEquals(1, timer.getCount()); + assertTrue(timer.getMeanRate() > 0.0); + } + } + + @Test + public void testMetricUsage() throws SQLException + { + assumeFalse(Os.isFamily(Os.FAMILY_WINDOWS)); + MetricRegistry metricRegistry = new MetricRegistry(); + + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(1); + config.setMaximumPoolSize(1); + config.setMetricRegistry(metricRegistry); + config.setInitializationFailTimeout(0); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + try (Connection connection = ds.getConnection()) { + UtilityElf.quietlySleep(250L); + } + + Histogram histo = metricRegistry.getHistograms(new MetricFilter() { + /** {@inheritDoc} */ + @Override + public boolean matches(String name, Metric metric) + { + return name.equals(MetricRegistry.name("testMetricUsage", "pool", "Usage")); + } + }).values().iterator().next(); + + assertEquals(1, histo.getCount()); + double seventyFifth = histo.getSnapshot().get75thPercentile(); + assertTrue("Seventy-fith percentile less than 250ms: " + seventyFifth, seventyFifth >= 250.0); + } + } + + @Test + public void testHealthChecks() throws Exception + { + MetricRegistry metricRegistry = new MetricRegistry(); + HealthCheckRegistry healthRegistry = new HealthCheckRegistry(); + + HikariConfig config = newHikariConfig(); + config.setMaximumPoolSize(10); + config.setMetricRegistry(metricRegistry); + config.setHealthCheckRegistry(healthRegistry); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + config.addHealthCheckProperty("connectivityCheckTimeoutMs", "1000"); + config.addHealthCheckProperty("expected99thPercentileMs", "100"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + quietlySleep(TimeUnit.SECONDS.toMillis(2)); + + try (Connection connection = ds.getConnection()) { + // close immediately + } + + try (Connection connection = ds.getConnection()) { + // close immediately + } + + SortedMap<String, Result> healthChecks = healthRegistry.runHealthChecks(); + + Result connectivityResult = healthChecks.get("testHealthChecks.pool.ConnectivityCheck"); + assertTrue(connectivityResult.isHealthy()); + + Result slaResult = healthChecks.get("testHealthChecks.pool.Connection99Percent"); + assertTrue(slaResult.isHealthy()); + } + } + + @Test + public void testSetters1() throws Exception + { + try (HikariDataSource ds = newHikariDataSource()) { + ds.setMaximumPoolSize(1); + ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + MetricRegistry metricRegistry = new MetricRegistry(); + HealthCheckRegistry healthRegistry = new HealthCheckRegistry(); + + try { + try (Connection connection = ds.getConnection()) { + // close immediately + } + + // After the pool as started, we can only set them once... + ds.setMetricRegistry(metricRegistry); + ds.setHealthCheckRegistry(healthRegistry); + + // and never again... + ds.setMetricRegistry(metricRegistry); + fail("Should not have been allowed to set registry after pool started"); + } + catch (IllegalStateException ise) { + // pass + try { + ds.setHealthCheckRegistry(healthRegistry); + fail("Should not have been allowed to set registry after pool started"); + } + catch (IllegalStateException ise2) { + // pass + } + } + } + } + + @Test + public void testSetters2() throws Exception + { + try (HikariDataSource ds = newHikariDataSource()) { + ds.setMaximumPoolSize(1); + ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + MetricRegistry metricRegistry = new MetricRegistry(); + HealthCheckRegistry healthRegistry = new HealthCheckRegistry(); + + ds.setMetricRegistry(metricRegistry); + ds.setHealthCheckRegistry(healthRegistry); + + // before the pool is started, we can set it any number of times... + ds.setMetricRegistry(metricRegistry); + ds.setHealthCheckRegistry(healthRegistry); + + try (Connection connection = ds.getConnection()) { + + // after the pool is started, we cannot set it any more + ds.setMetricRegistry(metricRegistry); + fail("Should not have been allowed to set registry after pool started"); + } + catch (IllegalStateException ise) { + // pass + } + } + } + + @Test + public void testSetters3() throws Exception + { + try (HikariDataSource ds = newHikariDataSource()) { + ds.setMaximumPoolSize(1); + ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + MetricRegistry metricRegistry = new MetricRegistry(); + MetricsTrackerFactory metricsTrackerFactory = new CodahaleMetricsTrackerFactory(metricRegistry); + + try (Connection connection = ds.getConnection()) { + + // After the pool as started, we can only set them once... + ds.setMetricsTrackerFactory(metricsTrackerFactory); + + // and never again... + ds.setMetricsTrackerFactory(metricsTrackerFactory); + fail("Should not have been allowed to set metricsTrackerFactory after pool started"); + } + catch (IllegalStateException ise) { + // pass + try { + // and never again... (even when calling another method) + ds.setMetricRegistry(metricRegistry); + fail("Should not have been allowed to set registry after pool started"); + } + catch (IllegalStateException ise2) { + // pass + } + } + } + } + + @Test + public void testSetters4() throws Exception + { + try (HikariDataSource ds = newHikariDataSource()) { + ds.setMaximumPoolSize(1); + ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + MetricRegistry metricRegistry = new MetricRegistry(); + + // before the pool is started, we can set it any number of times using either setter + ds.setMetricRegistry(metricRegistry); + ds.setMetricRegistry(metricRegistry); + ds.setMetricRegistry(metricRegistry); + + try (Connection connection = ds.getConnection()) { + + // after the pool is started, we cannot set it any more + ds.setMetricRegistry(metricRegistry); + fail("Should not have been allowed to set registry after pool started"); + } + catch (IllegalStateException ise) { + // pass + } + } + } + + @Test + public void testSetters5() throws Exception + { + try (HikariDataSource ds = newHikariDataSource()) { + ds.setMaximumPoolSize(1); + ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + MetricRegistry metricRegistry = new MetricRegistry(); + MetricsTrackerFactory metricsTrackerFactory = new CodahaleMetricsTrackerFactory(metricRegistry); + + // before the pool is started, we can set it any number of times using either setter + ds.setMetricsTrackerFactory(metricsTrackerFactory); + ds.setMetricsTrackerFactory(metricsTrackerFactory); + ds.setMetricsTrackerFactory(metricsTrackerFactory); + + try (Connection connection = ds.getConnection()) { + + // after the pool is started, we cannot set it any more + ds.setMetricsTrackerFactory(metricsTrackerFactory); + fail("Should not have been allowed to set registry factory after pool started"); + } + catch (IllegalStateException ise) { + // pass + } + } + } +} diff --git a/src/test/java/com/zaxxer/hikari/pool/TestPropertySetter.java b/src/test/java/com/zaxxer/hikari/pool/TestPropertySetter.java new file mode 100644 index 0000000..fb10446 --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/pool/TestPropertySetter.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2013, 2014 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package com.zaxxer.hikari.pool; + +import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; +import java.util.Properties; +import java.util.Set; + +import javax.sql.DataSource; + +import org.junit.Test; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.util.PropertyElf; + +public class TestPropertySetter +{ + @Test + public void testProperty1() throws Exception + { + Properties propfile1 = new Properties(); + propfile1.load(TestPropertySetter.class.getResourceAsStream("/propfile1.properties")); + HikariConfig config = new HikariConfig(propfile1); + config.validate(); + + assertEquals(5, config.getMinimumIdle()); + assertEquals("SELECT 1", config.getConnectionTestQuery()); + } + + @Test + public void testProperty2() throws Exception + { + Properties propfile2 = new Properties(); + propfile2.load(TestPropertySetter.class.getResourceAsStream("/propfile2.properties")); + HikariConfig config = new HikariConfig(propfile2); + config.validate(); + + Class<?> clazz = this.getClass().getClassLoader().loadClass(config.getDataSourceClassName()); + DataSource dataSource = (DataSource) clazz.newInstance(); + PropertyElf.setTargetFromProperties(dataSource, config.getDataSourceProperties()); + } + + @Test + public void testObjectProperty() throws Exception + { + HikariConfig config = newHikariConfig(); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + PrintWriter writer = new PrintWriter(new ByteArrayOutputStream()); + config.addDataSourceProperty("logWriter", writer); + + Class<?> clazz = this.getClass().getClassLoader().loadClass(config.getDataSourceClassName()); + DataSource dataSource = (DataSource) clazz.newInstance(); + PropertyElf.setTargetFromProperties(dataSource, config.getDataSourceProperties()); + + assertSame(PrintWriter.class, dataSource.getLogWriter().getClass()); + } + + @Test + public void testPropertyUpperCase() throws Exception + { + Properties propfile3 = new Properties(); + propfile3.load(TestPropertySetter.class.getResourceAsStream("/propfile3.properties")); + HikariConfig config = new HikariConfig(propfile3); + config.validate(); + + Class<?> clazz = this.getClass().getClassLoader().loadClass(config.getDataSourceClassName()); + DataSource dataSource = (DataSource) clazz.newInstance(); + PropertyElf.setTargetFromProperties(dataSource, config.getDataSourceProperties()); + } + + @Test + public void testGetPropertyNames() throws Exception + { + Set<String> propertyNames = PropertyElf.getPropertyNames(HikariConfig.class); + assertTrue(propertyNames.contains("dataSourceClassName")); + } + + @Test + public void testSetNonExistantPropertyName() throws Exception + { + try { + Properties props = new Properties(); + props.put("what", "happened"); + PropertyElf.setTargetFromProperties(new HikariConfig(), props); + fail(); + } + catch (RuntimeException e) { + } + } +} diff --git a/src/test/java/com/zaxxer/hikari/pool/TestProxies.java b/src/test/java/com/zaxxer/hikari/pool/TestProxies.java new file mode 100644 index 0000000..96f4066 --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/pool/TestProxies.java @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2013, 2014 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package com.zaxxer.hikari.pool; + +import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import com.zaxxer.hikari.mocks.StubConnection; +import com.zaxxer.hikari.mocks.StubStatement; + +public class TestProxies +{ + @Test + public void testProxyCreation() throws SQLException + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(0); + config.setMaximumPoolSize(1); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + Connection conn = ds.getConnection(); + + assertNotNull(conn.createStatement(ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE)); + assertNotNull(conn.createStatement(ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.HOLD_CURSORS_OVER_COMMIT)); + assertNotNull(conn.prepareCall("some sql")); + assertNotNull(conn.prepareCall("some sql", ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE)); + assertNotNull(conn.prepareCall("some sql", ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.HOLD_CURSORS_OVER_COMMIT)); + assertNotNull(conn.prepareStatement("some sql", PreparedStatement.NO_GENERATED_KEYS)); + assertNotNull(conn.prepareStatement("some sql", new int[3])); + assertNotNull(conn.prepareStatement("some sql", new String[3])); + assertNotNull(conn.prepareStatement("some sql", ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE)); + assertNotNull(conn.prepareStatement("some sql", ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.HOLD_CURSORS_OVER_COMMIT)); + assertNotNull(conn.toString()); + + assertTrue(conn.isWrapperFor(Connection.class)); + assertTrue(conn.isValid(10)); + assertFalse(conn.isClosed()); + assertTrue(conn.unwrap(StubConnection.class) instanceof StubConnection); + try { + conn.unwrap(TestProxies.class); + fail(); + } + catch (SQLException e) { + // pass + } + } + } + + @Test + public void testStatementProxy() throws SQLException + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(0); + config.setMaximumPoolSize(1); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + Connection conn = ds.getConnection(); + + PreparedStatement stmt = conn.prepareStatement("some sql"); + stmt.executeQuery(); + stmt.executeQuery("some sql"); + assertFalse(stmt.isClosed()); + assertNotNull(stmt.getGeneratedKeys()); + assertNotNull(stmt.getResultSet()); + assertNotNull(stmt.getConnection()); + assertTrue(stmt.unwrap(StubStatement.class) instanceof StubStatement); + try { + stmt.unwrap(TestProxies.class); + fail(); + } + catch (SQLException e) { + // pass + } + } + } + + @Test + public void testStatementExceptions() throws SQLException + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(0); + config.setMaximumPoolSize(1); + config.setConnectionTimeout(TimeUnit.SECONDS.toMillis(1)); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + Connection conn = ds.getConnection(); + StubConnection stubConnection = conn.unwrap(StubConnection.class); + stubConnection.throwException = true; + + try { + conn.createStatement(); + fail(); + } + catch (SQLException e) { + // pass + } + + try { + conn.createStatement(0, 0); + fail(); + } + catch (SQLException e) { + // pass + } + + try { + conn.createStatement(0, 0, 0); + fail(); + } + catch (SQLException e) { + // pass + } + + try { + conn.prepareCall(""); + fail(); + } + catch (SQLException e) { + // pass + } + + try { + conn.prepareCall("", 0, 0); + fail(); + } + catch (SQLException e) { + // pass + } + + try { + conn.prepareCall("", 0, 0, 0); + fail(); + } + catch (SQLException e) { + // pass + } + + try { + conn.prepareStatement(""); + fail(); + } + catch (SQLException e) { + // pass + } + + try { + conn.prepareStatement("", 0); + fail(); + } + catch (SQLException e) { + // pass + } + + try { + conn.prepareStatement("", new int[0]); + fail(); + } + catch (SQLException e) { + // pass + } + + try { + conn.prepareStatement("", new String[0]); + fail(); + } + catch (SQLException e) { + // pass + } + + try { + conn.prepareStatement("", 0, 0); + fail(); + } + catch (SQLException e) { + // pass + } + + try { + conn.prepareStatement("", 0, 0, 0); + fail(); + } + catch (SQLException e) { + // pass + } + } + } + + @Test + public void testOtherExceptions() throws SQLException + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(0); + config.setMaximumPoolSize(1); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + try (Connection conn = ds.getConnection()) { + StubConnection stubConnection = conn.unwrap(StubConnection.class); + stubConnection.throwException = true; + + try { + conn.setTransactionIsolation(Connection.TRANSACTION_NONE); + fail(); + } + catch (SQLException e) { + // pass + } + + try { + conn.isReadOnly(); + fail(); + } + catch (SQLException e) { + // pass + } + + try { + conn.setReadOnly(false); + fail(); + } + catch (SQLException e) { + // pass + } + + try { + conn.setCatalog(""); + fail(); + } + catch (SQLException e) { + // pass + } + + try { + conn.setAutoCommit(false); + fail(); + } + catch (SQLException e) { + // pass + } + + try { + conn.clearWarnings(); + fail(); + } + catch (SQLException e) { + // pass + } + + try { + conn.isValid(0); + fail(); + } + catch (SQLException e) { + // pass + } + + try { + conn.isWrapperFor(getClass()); + fail(); + } + catch (SQLException e) { + // pass + } + + try { + conn.unwrap(getClass()); + fail(); + } + catch (SQLException e) { + // pass + } + + try { + conn.close(); + fail(); + } + catch (SQLException e) { + // pass + } + + try { + assertFalse(conn.isValid(0)); + } + catch (SQLException e) { + fail(); + } + } + } + } +} diff --git a/src/test/java/com/zaxxer/hikari/pool/TestValidation.java b/src/test/java/com/zaxxer/hikari/pool/TestValidation.java new file mode 100644 index 0000000..46ba779 --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/pool/TestValidation.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2014 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zaxxer.hikari.pool; + +import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; +import static com.zaxxer.hikari.pool.TestElf.setSlf4jTargetStream; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import com.zaxxer.hikari.HikariConfig; + +/** + * @author Brett Wooldridge + */ +public class TestValidation +{ + @Test + public void validateLoadProperties() + { + System.setProperty("hikaricp.configurationFile", "/propfile1.properties"); + HikariConfig config = newHikariConfig(); + System.clearProperty("hikaricp.configurationFile"); + assertEquals(5, config.getMinimumIdle()); + } + + @Test + public void validateMissingProperties() + { + try { + HikariConfig config = new HikariConfig("missing"); + config.validate(); + } + catch (IllegalArgumentException ise) { + assertTrue(ise.getMessage().contains("property file")); + } + } + + @Test + public void validateMissingDS() + { + try { + HikariConfig config = newHikariConfig(); + config.validate(); + fail(); + } + catch (IllegalArgumentException ise) { + assertTrue(ise.getMessage().contains("dataSource or dataSourceClassName or jdbcUrl is required.")); + } + } + + @Test + public void validateMissingUrl() + { + try { + HikariConfig config = newHikariConfig(); + config.setDriverClassName("com.zaxxer.hikari.mocks.StubDriver"); + config.validate(); + fail(); + } + catch (IllegalArgumentException ise) { + assertTrue(ise.getMessage().contains("jdbcUrl is required with driverClassName")); + } + } + + @Test + public void validateDriverAndUrl() + { + try { + HikariConfig config = newHikariConfig(); + config.setDriverClassName("com.zaxxer.hikari.mocks.StubDriver"); + config.setJdbcUrl("jdbc:stub"); + config.validate(); + } + catch (Throwable t) { + fail(t.getMessage()); + } + } + + @Test + public void validateBadDriver() + { + try { + HikariConfig config = newHikariConfig(); + config.setDriverClassName("invalid"); + config.validate(); + fail(); + } + catch (RuntimeException ise) { + assertTrue(ise.getMessage().contains("class of driverClassName ")); + } + } + + @Test + public void validateInvalidConnectionTimeout() + { + try { + HikariConfig config = newHikariConfig(); + config.setConnectionTimeout(10L); + fail(); + } + catch (IllegalArgumentException ise) { + assertTrue(ise.getMessage().contains("connectionTimeout cannot be less than 250ms")); + } + } + + @Test + public void validateInvalidValidationTimeout() + { + try { + HikariConfig config = newHikariConfig(); + config.setValidationTimeout(10L); + fail(); + } + catch (IllegalArgumentException ise) { + assertTrue(ise.getMessage().contains("validationTimeout cannot be less than 250ms")); + } + } + + @Test + public void validateInvalidIdleTimeout() + { + try { + HikariConfig config = newHikariConfig(); + config.setIdleTimeout(-1L); + fail("negative idle timeout accepted"); + } + catch (IllegalArgumentException ise) { + assertTrue(ise.getMessage().contains("idleTimeout cannot be negative")); + } + } + + @Test + public void validateIdleTimeoutTooSmall() + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream(baos, true); + setSlf4jTargetStream(HikariConfig.class, ps); + + HikariConfig config = newHikariConfig(); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + config.setIdleTimeout(TimeUnit.SECONDS.toMillis(5)); + config.validate(); + assertTrue(new String(baos.toByteArray()).contains("less than 10000ms")); + } + + @Test + public void validateIdleTimeoutExceedsLifetime() + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream(baos, true); + setSlf4jTargetStream(HikariConfig.class, ps); + + HikariConfig config = newHikariConfig(); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + config.setMaxLifetime(TimeUnit.MINUTES.toMillis(2)); + config.setIdleTimeout(TimeUnit.MINUTES.toMillis(3)); + config.validate(); + + String s = new String(baos.toByteArray()); + assertTrue("idleTimeout is close to or more than maxLifetime, disabling it." + s + "*", s.contains("is close to or more than maxLifetime")); + } + + @Test + public void validateInvalidMinIdle() + { + try { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(-1); + fail(); + } + catch (IllegalArgumentException ise) { + assertTrue(ise.getMessage().contains("minimumIdle cannot be negative")); + } + } + + @Test + public void validateInvalidMaxPoolSize() + { + try { + HikariConfig config = newHikariConfig(); + config.setMaximumPoolSize(0); + fail(); + } + catch (IllegalArgumentException ise) { + assertTrue(ise.getMessage().contains("maxPoolSize cannot be less than 1")); + } + } + + @Test + public void validateInvalidLifetime() + { + try { + HikariConfig config = newHikariConfig(); + config.setConnectionTimeout(Integer.MAX_VALUE); + config.setIdleTimeout(1000L); + config.setMaxLifetime(-1L); + config.setLeakDetectionThreshold(1000L); + config.validate(); + fail(); + } + catch (IllegalArgumentException ise) { + // pass + } + } + + @Test + public void validateInvalidLeakDetection() + { + try { + HikariConfig config = newHikariConfig(); + config.setLeakDetectionThreshold(1000L); + config.validate(); + fail(); + } + catch (IllegalArgumentException ise) { + // pass + } + } + + @Test + public void validateZeroConnectionTimeout() + { + try { + HikariConfig config = newHikariConfig(); + config.setConnectionTimeout(0); + config.validate(); + assertEquals(Integer.MAX_VALUE, config.getConnectionTimeout()); + } + catch (IllegalArgumentException ise) { + // pass + } + } +} diff --git a/src/test/java/com/zaxxer/hikari/pool/UnwrapTest.java b/src/test/java/com/zaxxer/hikari/pool/UnwrapTest.java new file mode 100644 index 0000000..d13c8f4 --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/pool/UnwrapTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2013 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.zaxxer.hikari.pool; + +import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; +import static com.zaxxer.hikari.pool.TestElf.getPool; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.sql.Connection; +import java.sql.SQLException; + +import org.junit.Test; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import com.zaxxer.hikari.mocks.StubConnection; +import com.zaxxer.hikari.mocks.StubDataSource; + +/** + * @author Brett Wooldridge + */ +public class UnwrapTest +{ + @Test + public void testUnwrapConnection() throws SQLException + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(1); + config.setMaximumPoolSize(1); + config.setInitializationFailTimeout(0); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + ds.getConnection().close(); + assertSame("Idle connections not as expected", 1, getPool(ds).getIdleConnections()); + + Connection connection = ds.getConnection(); + assertNotNull(connection); + + StubConnection unwrapped = connection.unwrap(StubConnection.class); + assertTrue("unwrapped connection is not instance of StubConnection: " + unwrapped, (unwrapped != null && unwrapped instanceof StubConnection)); + } + } + + @Test + public void testUnwrapDataSource() throws SQLException + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(1); + config.setMaximumPoolSize(1); + config.setInitializationFailTimeout(0); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + StubDataSource unwrap = ds.unwrap(StubDataSource.class); + assertNotNull(unwrap); + assertTrue(unwrap instanceof StubDataSource); + + assertTrue(ds.isWrapperFor(HikariDataSource.class)); + assertTrue(ds.unwrap(HikariDataSource.class) instanceof HikariDataSource); + + assertFalse(ds.isWrapperFor(getClass())); + try { + ds.unwrap(getClass()); + } + catch (SQLException e) { + assertTrue(e.getMessage().contains("Wrapped DataSource")); + } + } + } +} |