From bd7b6679cea5620446718911de7a6764f81a9a7a Mon Sep 17 00:00:00 2001 From: Apollon Oikonomopoulos Date: Wed, 1 Mar 2017 14:21:59 +0200 Subject: New upstream version 2.6.0 --- .../java/com/zaxxer/hikari/db/BasicPoolTest.java | 149 +++ .../dropwizard/CodaHaleMetricsTrackerTest.java | 39 + .../metrics/prometheus/HikariCPCollectorTest.java | 125 ++ .../prometheus/PrometheusMetricsTrackerTest.java | 78 ++ .../com/zaxxer/hikari/mocks/MockDataSource.java | 162 +++ .../zaxxer/hikari/mocks/StubBaseConnection.java | 47 + .../com/zaxxer/hikari/mocks/StubConnection.java | 486 ++++++++ .../com/zaxxer/hikari/mocks/StubDataSource.java | 149 +++ .../java/com/zaxxer/hikari/mocks/StubDriver.java | 96 ++ .../zaxxer/hikari/mocks/StubPreparedStatement.java | 671 ++++++++++ .../com/zaxxer/hikari/mocks/StubResultSet.java | 1292 ++++++++++++++++++++ .../com/zaxxer/hikari/mocks/StubStatement.java | 387 ++++++ .../com/zaxxer/hikari/osgi/OSGiBundleTest.java | 91 ++ .../hikari/pool/ConcurrentCloseConnectionTest.java | 76 ++ .../pool/ConnectionPoolSizeVsThreadsTest.java | 198 +++ .../hikari/pool/ConnectionRaceConditionTest.java | 106 ++ .../zaxxer/hikari/pool/ConnectionStateTest.java | 170 +++ .../java/com/zaxxer/hikari/pool/ExceptionTest.java | 119 ++ .../zaxxer/hikari/pool/HouseKeeperCleanupTest.java | 84 ++ .../java/com/zaxxer/hikari/pool/IsolationTest.java | 74 ++ .../com/zaxxer/hikari/pool/JdbcDriverTest.java | 86 ++ src/test/java/com/zaxxer/hikari/pool/MiscTest.java | 134 ++ .../java/com/zaxxer/hikari/pool/PostgresTest.java | 231 ++++ .../java/com/zaxxer/hikari/pool/RampUpDown.java | 75 ++ .../java/com/zaxxer/hikari/pool/ShutdownTest.java | 356 ++++++ .../java/com/zaxxer/hikari/pool/StatementTest.java | 119 ++ .../com/zaxxer/hikari/pool/TestConcurrentBag.java | 117 ++ .../hikari/pool/TestConnectionCloseBlocking.java | 98 ++ .../hikari/pool/TestConnectionTimeoutRetry.java | 277 +++++ .../com/zaxxer/hikari/pool/TestConnections.java | 621 ++++++++++ src/test/java/com/zaxxer/hikari/pool/TestElf.java | 177 +++ .../java/com/zaxxer/hikari/pool/TestHibernate.java | 57 + src/test/java/com/zaxxer/hikari/pool/TestJNDI.java | 146 +++ .../java/com/zaxxer/hikari/pool/TestMBean.java | 44 + .../java/com/zaxxer/hikari/pool/TestMetrics.java | 303 +++++ .../com/zaxxer/hikari/pool/TestPropertySetter.java | 111 ++ .../java/com/zaxxer/hikari/pool/TestProxies.java | 324 +++++ .../com/zaxxer/hikari/pool/TestValidation.java | 253 ++++ .../java/com/zaxxer/hikari/pool/UnwrapTest.java | 90 ++ .../com/zaxxer/hikari/util/ClockSourceTest.java | 60 + .../java/com/zaxxer/hikari/util/TestFastList.java | 172 +++ .../hikari/util/TomcatConcurrentBagLeakTest.java | 328 +++++ 42 files changed, 8778 insertions(+) create mode 100644 src/test/java/com/zaxxer/hikari/db/BasicPoolTest.java create mode 100755 src/test/java/com/zaxxer/hikari/metrics/dropwizard/CodaHaleMetricsTrackerTest.java create mode 100644 src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java create mode 100644 src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java create mode 100644 src/test/java/com/zaxxer/hikari/mocks/MockDataSource.java create mode 100644 src/test/java/com/zaxxer/hikari/mocks/StubBaseConnection.java create mode 100644 src/test/java/com/zaxxer/hikari/mocks/StubConnection.java create mode 100755 src/test/java/com/zaxxer/hikari/mocks/StubDataSource.java create mode 100644 src/test/java/com/zaxxer/hikari/mocks/StubDriver.java create mode 100644 src/test/java/com/zaxxer/hikari/mocks/StubPreparedStatement.java create mode 100644 src/test/java/com/zaxxer/hikari/mocks/StubResultSet.java create mode 100644 src/test/java/com/zaxxer/hikari/mocks/StubStatement.java create mode 100644 src/test/java/com/zaxxer/hikari/osgi/OSGiBundleTest.java create mode 100644 src/test/java/com/zaxxer/hikari/pool/ConcurrentCloseConnectionTest.java create mode 100755 src/test/java/com/zaxxer/hikari/pool/ConnectionPoolSizeVsThreadsTest.java create mode 100755 src/test/java/com/zaxxer/hikari/pool/ConnectionRaceConditionTest.java create mode 100644 src/test/java/com/zaxxer/hikari/pool/ConnectionStateTest.java create mode 100644 src/test/java/com/zaxxer/hikari/pool/ExceptionTest.java create mode 100644 src/test/java/com/zaxxer/hikari/pool/HouseKeeperCleanupTest.java create mode 100644 src/test/java/com/zaxxer/hikari/pool/IsolationTest.java create mode 100644 src/test/java/com/zaxxer/hikari/pool/JdbcDriverTest.java create mode 100644 src/test/java/com/zaxxer/hikari/pool/MiscTest.java create mode 100644 src/test/java/com/zaxxer/hikari/pool/PostgresTest.java create mode 100755 src/test/java/com/zaxxer/hikari/pool/RampUpDown.java create mode 100644 src/test/java/com/zaxxer/hikari/pool/ShutdownTest.java create mode 100644 src/test/java/com/zaxxer/hikari/pool/StatementTest.java create mode 100644 src/test/java/com/zaxxer/hikari/pool/TestConcurrentBag.java create mode 100644 src/test/java/com/zaxxer/hikari/pool/TestConnectionCloseBlocking.java create mode 100644 src/test/java/com/zaxxer/hikari/pool/TestConnectionTimeoutRetry.java create mode 100644 src/test/java/com/zaxxer/hikari/pool/TestConnections.java create mode 100644 src/test/java/com/zaxxer/hikari/pool/TestElf.java create mode 100644 src/test/java/com/zaxxer/hikari/pool/TestHibernate.java create mode 100644 src/test/java/com/zaxxer/hikari/pool/TestJNDI.java create mode 100644 src/test/java/com/zaxxer/hikari/pool/TestMBean.java create mode 100644 src/test/java/com/zaxxer/hikari/pool/TestMetrics.java create mode 100644 src/test/java/com/zaxxer/hikari/pool/TestPropertySetter.java create mode 100644 src/test/java/com/zaxxer/hikari/pool/TestProxies.java create mode 100644 src/test/java/com/zaxxer/hikari/pool/TestValidation.java create mode 100644 src/test/java/com/zaxxer/hikari/pool/UnwrapTest.java create mode 100644 src/test/java/com/zaxxer/hikari/util/ClockSourceTest.java create mode 100644 src/test/java/com/zaxxer/hikari/util/TestFastList.java create mode 100644 src/test/java/com/zaxxer/hikari/util/TomcatConcurrentBagLeakTest.java (limited to 'src/test/java/com/zaxxer/hikari') diff --git a/src/test/java/com/zaxxer/hikari/db/BasicPoolTest.java b/src/test/java/com/zaxxer/hikari/db/BasicPoolTest.java new file mode 100644 index 0000000..c9040b0 --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/db/BasicPoolTest.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2016 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.db; + +import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; +import static com.zaxxer.hikari.pool.TestElf.getPool; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +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.pool.HikariPool; + +/** + * @author brettw + * + */ +public class BasicPoolTest +{ + @Before + public void setup() throws SQLException + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(1); + config.setMaximumPoolSize(2); + config.setConnectionTestQuery("SELECT 1"); + config.setDataSourceClassName("org.h2.jdbcx.JdbcDataSource"); + config.addDataSourceProperty("url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"); + + try (HikariDataSource ds = new HikariDataSource(config); + Connection conn = ds.getConnection(); + Statement stmt = conn.createStatement()) { + stmt.executeUpdate("DROP TABLE IF EXISTS basic_pool_test"); + stmt.executeUpdate("CREATE TABLE basic_pool_test (" + + "id INTEGER NOT NULL IDENTITY PRIMARY KEY, " + + "timestamp TIMESTAMP, " + + "string VARCHAR(128), " + + "string_from_number NUMERIC " + + ")"); + } + } + + @Test + public void testIdleTimeout() throws InterruptedException, SQLException + { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(5); + config.setMaximumPoolSize(10); + config.setConnectionTestQuery("SELECT 1"); + config.setDataSourceClassName("org.h2.jdbcx.JdbcDataSource"); + config.addDataSourceProperty("url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"); + + System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "1000"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + System.clearProperty("com.zaxxer.hikari.housekeeping.periodMs"); + + SECONDS.sleep(1); + + HikariPool pool = getPool(ds); + + ds.setIdleTimeout(3000); + + assertEquals("Total connections not as expected", 5, pool.getTotalConnections()); + assertEquals("Idle connections not as expected", 5, pool.getIdleConnections()); + + try (Connection connection = ds.getConnection()) { + Assert.assertNotNull(connection); + + MILLISECONDS.sleep(1500); + + assertEquals("Second total connections not as expected", 6, pool.getTotalConnections()); + assertEquals("Second idle connections not as expected", 5, pool.getIdleConnections()); + } + + assertEquals("Idle connections not as expected", 6, pool.getIdleConnections()); + + SECONDS.sleep(2); + + assertEquals("Third total connections not as expected", 5, pool.getTotalConnections()); + assertEquals("Third idle connections not as expected", 5, pool.getIdleConnections()); + } + } + + @Test + public void testIdleTimeout2() throws InterruptedException, SQLException + { + HikariConfig config = newHikariConfig(); + config.setMaximumPoolSize(50); + config.setConnectionTestQuery("SELECT 1"); + config.setDataSourceClassName("org.h2.jdbcx.JdbcDataSource"); + config.addDataSourceProperty("url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"); + + System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "1000"); + + try (HikariDataSource ds = new HikariDataSource(config)) { + System.clearProperty("com.zaxxer.hikari.housekeeping.periodMs"); + + SECONDS.sleep(1); + + HikariPool pool = getPool(ds); + + ds.setIdleTimeout(3000); + + assertEquals("Total connections not as expected", 50, pool.getTotalConnections()); + assertEquals("Idle connections not as expected", 50, pool.getIdleConnections()); + + try (Connection connection = ds.getConnection()) { + assertNotNull(connection); + + MILLISECONDS.sleep(1500); + + assertEquals("Second total connections not as expected", 50, pool.getTotalConnections()); + assertEquals("Second idle connections not as expected", 49, pool.getIdleConnections()); + } + + assertEquals("Idle connections not as expected", 50, pool.getIdleConnections()); + + SECONDS.sleep(3); + + assertEquals("Third total connections not as expected", 50, pool.getTotalConnections()); + assertEquals("Third idle connections not as expected", 50, pool.getIdleConnections()); + } + } +} diff --git a/src/test/java/com/zaxxer/hikari/metrics/dropwizard/CodaHaleMetricsTrackerTest.java b/src/test/java/com/zaxxer/hikari/metrics/dropwizard/CodaHaleMetricsTrackerTest.java new file mode 100755 index 0000000..8e2164c --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/metrics/dropwizard/CodaHaleMetricsTrackerTest.java @@ -0,0 +1,39 @@ +package com.zaxxer.hikari.metrics.dropwizard; + +import static org.mockito.Mockito.verify; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import com.codahale.metrics.MetricRegistry; + +@RunWith(MockitoJUnitRunner.class) +public class CodaHaleMetricsTrackerTest { + + @Mock + public MetricRegistry mockMetricRegistry; + + private CodaHaleMetricsTracker testee; + + @Before + public void setup(){ + testee = new CodaHaleMetricsTracker("mypool", null, mockMetricRegistry); + } + + @Test + public void close() throws Exception { + testee.close(); + + verify(mockMetricRegistry).remove("mypool.pool.Wait"); + verify(mockMetricRegistry).remove("mypool.pool.Usage"); + verify(mockMetricRegistry).remove("mypool.pool.ConnectionCreation"); + verify(mockMetricRegistry).remove("mypool.pool.ConnectionTimeoutRate"); + verify(mockMetricRegistry).remove("mypool.pool.TotalConnections"); + verify(mockMetricRegistry).remove("mypool.pool.IdleConnections"); + verify(mockMetricRegistry).remove("mypool.pool.ActiveConnections"); + verify(mockMetricRegistry).remove("mypool.pool.PendingConnections"); + } +} diff --git a/src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java b/src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java new file mode 100644 index 0000000..46b2e74 --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java @@ -0,0 +1,125 @@ +/* + * 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.metrics.prometheus; + +import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; +import static com.zaxxer.hikari.util.UtilityElf.quietlySleep; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.sql.Connection; + +import org.junit.Test; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import com.zaxxer.hikari.mocks.StubConnection; + +import io.prometheus.client.CollectorRegistry; + +public class HikariCPCollectorTest { + @Test + public void noConnection() throws Exception { + HikariConfig config = newHikariConfig(); + config.setMinimumIdle(0); + config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory()); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + StubConnection.slowCreate = true; + try (HikariDataSource ds = new HikariDataSource(config)) { + assertThat(getValue("hikaricp_active_connections", "noConnection"), is(0.0)); + assertThat(getValue("hikaricp_idle_connections", "noConnection"), is(0.0)); + assertThat(getValue("hikaricp_pending_threads", "noConnection"), is(0.0)); + assertThat(getValue("hikaricp_connections", "noConnection"), is(0.0)); + } + finally { + StubConnection.slowCreate = false; + } + } + + @Test + public void noConnectionWithoutPoolName() throws Exception { + HikariConfig config = new HikariConfig(); + config.setMinimumIdle(0); + config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory()); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + StubConnection.slowCreate = true; + try (HikariDataSource ds = new HikariDataSource(config)) { + assertThat(getValue("hikaricp_active_connections", "HikariPool-1"), is(0.0)); + assertThat(getValue("hikaricp_idle_connections", "HikariPool-1"), is(0.0)); + assertThat(getValue("hikaricp_pending_threads", "HikariPool-1"), is(0.0)); + assertThat(getValue("hikaricp_connections", "HikariPool-1"), is(0.0)); + } + finally { + StubConnection.slowCreate = false; + } + } + + @Test + public void connection1() throws Exception { + HikariConfig config = newHikariConfig(); + config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory()); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + config.setMaximumPoolSize(1); + + StubConnection.slowCreate = true; + try (HikariDataSource ds = new HikariDataSource(config); + Connection connection1 = ds.getConnection()) { + + quietlySleep(1000); + + assertThat(getValue("hikaricp_active_connections", "connection1"), is(1.0)); + assertThat(getValue("hikaricp_idle_connections", "connection1"), is(0.0)); + assertThat(getValue("hikaricp_pending_threads", "connection1"), is(0.0)); + assertThat(getValue("hikaricp_connections", "connection1"), is(1.0)); + } + finally { + StubConnection.slowCreate = false; + } + } + + @Test + public void connectionClosed() throws Exception { + HikariConfig config = newHikariConfig(); + config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory()); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + config.setMaximumPoolSize(1); + + StubConnection.slowCreate = true; + try (HikariDataSource ds = new HikariDataSource(config)) { + try (Connection connection1 = ds.getConnection()) { + // close immediately + } + + assertThat(getValue("hikaricp_active_connections", "connectionClosed"), is(0.0)); + assertThat(getValue("hikaricp_idle_connections", "connectionClosed"), is(1.0)); + assertThat(getValue("hikaricp_pending_threads", "connectionClosed"), is(0.0)); + assertThat(getValue("hikaricp_connections", "connectionClosed"), is(1.0)); + } + finally { + StubConnection.slowCreate = false; + } + } + + private double getValue(String name, String poolName) { + String[] labelNames = {"pool"}; + String[] labelValues = {poolName}; + return CollectorRegistry.defaultRegistry.getSampleValue(name, labelNames, labelValues); + } + +} diff --git a/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java b/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java new file mode 100644 index 0000000..a7b0b03 --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java @@ -0,0 +1,78 @@ +/* + * 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.metrics.prometheus; + +import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.sql.Connection; +import java.sql.SQLTransientConnectionException; + +import org.junit.Test; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; + +import io.prometheus.client.CollectorRegistry; + +public class PrometheusMetricsTrackerTest { + @Test + public void recordConnectionTimeout() throws Exception { + HikariConfig config = newHikariConfig(); + config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory()); + config.setJdbcUrl("jdbc:h2:mem:"); + config.setMaximumPoolSize(2); + config.setConnectionTimeout(250); + + String[] labelNames = {"pool"}; + String[] labelValues = {config.getPoolName()}; + + try (HikariDataSource hikariDataSource = new HikariDataSource(config)) { + try (Connection connection1 = hikariDataSource.getConnection(); + Connection connection2 = hikariDataSource.getConnection()) { + try (Connection connection3 = hikariDataSource.getConnection()) { + } + catch (SQLTransientConnectionException ignored) { + } + } + + assertThat(CollectorRegistry.defaultRegistry.getSampleValue( + "hikaricp_connection_timeout_count", + labelNames, + labelValues), is(1.0)); + assertThat(CollectorRegistry.defaultRegistry.getSampleValue( + "hikaricp_connection_acquired_nanos_count", + labelNames, + labelValues), is(equalTo(2.0))); + assertTrue(CollectorRegistry.defaultRegistry.getSampleValue( + "hikaricp_connection_acquired_nanos_sum", + labelNames, + labelValues) > 0.0); + assertThat(CollectorRegistry.defaultRegistry.getSampleValue( + "hikaricp_connection_usage_millis_count", + labelNames, + labelValues), is(equalTo(2.0))); + assertTrue(CollectorRegistry.defaultRegistry.getSampleValue( + "hikaricp_connection_usage_millis_sum", + labelNames, + labelValues) > 0.0); + } + } +} diff --git a/src/test/java/com/zaxxer/hikari/mocks/MockDataSource.java b/src/test/java/com/zaxxer/hikari/mocks/MockDataSource.java new file mode 100644 index 0000000..558f9fb --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/mocks/MockDataSource.java @@ -0,0 +1,162 @@ +/* + * 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.mocks; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.PrintWriter; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.Statement; +import java.util.logging.Logger; + +import javax.sql.DataSource; + +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +/** + * + * @author Brett Wooldridge + */ +public class MockDataSource implements DataSource +{ + @Override + public Connection getConnection() throws SQLException + { + return createMockConnection(); + } + + @Override + public Connection getConnection(String username, String password) throws SQLException + { + return getConnection(); + } + + @Override + public PrintWriter getLogWriter() throws SQLException + { + return null; + } + + @Override + public void setLogWriter(PrintWriter out) throws SQLException + { + } + + @Override + public void setLoginTimeout(int seconds) throws SQLException + { + } + + @Override + public int getLoginTimeout() throws SQLException + { + return 0; + } + + public Logger getParentLogger() throws SQLFeatureNotSupportedException + { + return null; + } + + @Override + public T unwrap(Class iface) throws SQLException + { + return null; + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException + { + return false; + } + + public static Connection createMockConnection() throws SQLException { + // Setup mock connection + final Connection mockConnection = mock(Connection.class); + + // Autocommit is always true by default + when(mockConnection.getAutoCommit()).thenReturn(true); + + // Handle Connection.createStatement() + Statement statement = mock(Statement.class); + when(mockConnection.createStatement()).thenReturn(statement); + when(mockConnection.createStatement(anyInt(), anyInt())).thenReturn(statement); + when(mockConnection.createStatement(anyInt(), anyInt(), anyInt())).thenReturn(statement); + when(mockConnection.isValid(anyInt())).thenReturn(true); + + // Handle Connection.prepareStatement() + PreparedStatement mockPreparedStatement = mock(PreparedStatement.class); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockConnection.prepareStatement(anyString(), anyInt())).thenReturn(mockPreparedStatement); + when(mockConnection.prepareStatement(anyString(), any(int[].class))).thenReturn(mockPreparedStatement); + when(mockConnection.prepareStatement(anyString(), any(String[].class))).thenReturn(mockPreparedStatement); + when(mockConnection.prepareStatement(anyString(), anyInt(), anyInt())).thenReturn(mockPreparedStatement); + when(mockConnection.prepareStatement(anyString(), anyInt(), anyInt(), anyInt())).thenReturn(mockPreparedStatement); + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable + { + return null; + } + }).doNothing().when(mockPreparedStatement).setInt(anyInt(), anyInt()); + + ResultSet mockResultSet = mock(ResultSet.class); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + when(mockResultSet.getString(anyInt())).thenReturn("aString"); + when(mockResultSet.next()).thenReturn(true); + + // Handle Connection.prepareCall() + CallableStatement mockCallableStatement = mock(CallableStatement.class); + when(mockConnection.prepareCall(anyString())).thenReturn(mockCallableStatement); + when(mockConnection.prepareCall(anyString(), anyInt(), anyInt())).thenReturn(mockCallableStatement); + when(mockConnection.prepareCall(anyString(), anyInt(), anyInt(), anyInt())).thenReturn(mockCallableStatement); + + // Handle Connection.close() +// doAnswer(new Answer() { +// public Void answer(InvocationOnMock invocation) throws Throwable { +// return null; +// } +// }).doThrow(new SQLException("Connection is already closed")).when(mockConnection).close(); + + // Handle Connection.commit() +// doAnswer(new Answer() { +// public Void answer(InvocationOnMock invocation) throws Throwable { +// return null; +// } +// }).doThrow(new SQLException("Transaction already commited")).when(mockConnection).commit(); + + // Handle Connection.rollback() +// doAnswer(new Answer() { +// public Void answer(InvocationOnMock invocation) throws Throwable { +// return null; +// } +// }).doThrow(new SQLException("Transaction already rolledback")).when(mockConnection).rollback(); + + return mockConnection; + } +} diff --git a/src/test/java/com/zaxxer/hikari/mocks/StubBaseConnection.java b/src/test/java/com/zaxxer/hikari/mocks/StubBaseConnection.java new file mode 100644 index 0000000..f88705e --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/mocks/StubBaseConnection.java @@ -0,0 +1,47 @@ +/* + * 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.mocks; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; + +public abstract class StubBaseConnection implements Connection +{ + public volatile boolean throwException; + + /** {@inheritDoc} */ + @Override + public Statement createStatement() throws SQLException + { + if (throwException) { + throw new SQLException(); + } + return new StubStatement(this); + } + + /** {@inheritDoc} */ + @Override + public PreparedStatement prepareStatement(String sql) throws SQLException + { + if (throwException) { + throw new SQLException(); + } + return new StubPreparedStatement(this); + } +} diff --git a/src/test/java/com/zaxxer/hikari/mocks/StubConnection.java b/src/test/java/com/zaxxer/hikari/mocks/StubConnection.java new file mode 100644 index 0000000..f96624b --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/mocks/StubConnection.java @@ -0,0 +1,486 @@ +/* + * 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.mocks; + +import java.sql.Array; +import java.sql.Blob; +import java.sql.CallableStatement; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.NClob; +import java.sql.PreparedStatement; +import java.sql.SQLClientInfoException; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Savepoint; +import java.sql.Statement; +import java.sql.Struct; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicInteger; + +import com.zaxxer.hikari.util.UtilityElf; + +/** + * + * @author Brett Wooldridge + */ +public class StubConnection extends StubBaseConnection implements Connection +{ + public static final AtomicInteger count = new AtomicInteger(); + public static volatile boolean slowCreate; + public static volatile boolean oldDriver; + + private static long foo; + private boolean autoCommit; + private int isolation = Connection.TRANSACTION_READ_COMMITTED; + private String catalog; + + static { + foo = System.currentTimeMillis(); + } + + public StubConnection() { + count.incrementAndGet(); + if (slowCreate) { + UtilityElf.quietlySleep(1000); + } + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override + public T unwrap(Class iface) throws SQLException + { + if (throwException) { + throw new SQLException(); + } + + if (iface.isInstance(this)) { + return (T) this; + } + + throw new SQLException("Wrapped connection is not an instance of " + iface); + } + + /** {@inheritDoc} */ + @Override + public boolean isWrapperFor(Class iface) throws SQLException + { + if (throwException) { + throw new SQLException(); + } + return false; + } + + /** {@inheritDoc} */ + @Override + public CallableStatement prepareCall(String sql) throws SQLException + { + if (throwException) { + throw new SQLException(); + } + return null; + } + + /** {@inheritDoc} */ + @Override + public String nativeSQL(String sql) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public void setAutoCommit(boolean autoCommit) throws SQLException + { + if (throwException) { + throw new SQLException(); + } + this.autoCommit = autoCommit; + } + + /** {@inheritDoc} */ + @Override + public boolean getAutoCommit() throws SQLException + { + return autoCommit; + } + + /** {@inheritDoc} */ + @Override + public void commit() throws SQLException + { + + } + + /** {@inheritDoc} */ + @Override + public void rollback() throws SQLException + { + + } + + /** {@inheritDoc} */ + @Override + public void close() throws SQLException + { + + } + + /** {@inheritDoc} */ + @Override + public boolean isClosed() throws SQLException + { + if (throwException) { + throw new SQLException(); + } + return false; + } + + /** {@inheritDoc} */ + @Override + public DatabaseMetaData getMetaData() throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public void setReadOnly(boolean readOnly) throws SQLException + { + if (throwException) { + throw new SQLException(); + } + } + + /** {@inheritDoc} */ + @Override + public boolean isReadOnly() throws SQLException + { + if (throwException) { + throw new SQLException(); + } + return false; + } + + /** {@inheritDoc} */ + @Override + public void setCatalog(String catalog) throws SQLException + { + if (throwException) { + throw new SQLException(); + } + this.catalog = catalog; + } + + /** {@inheritDoc} */ + @Override + public String getCatalog() throws SQLException + { + return catalog; + } + + /** {@inheritDoc} */ + @Override + public void setTransactionIsolation(int level) throws SQLException + { + if (throwException) { + throw new SQLException(); + } + this.isolation = level; + } + + /** {@inheritDoc} */ + @Override + public int getTransactionIsolation() throws SQLException + { + return isolation; + } + + /** {@inheritDoc} */ + @Override + public SQLWarning getWarnings() throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public void clearWarnings() throws SQLException + { + if (throwException) { + throw new SQLException(); + } + } + + /** {@inheritDoc} */ + @Override + public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException + { + if (throwException) { + throw new SQLException(); + } + return null; + } + + /** {@inheritDoc} */ + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException + { + if (throwException) { + throw new SQLException(); + } + return new StubPreparedStatement(this); + } + + /** {@inheritDoc} */ + @Override + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException + { + if (throwException) { + throw new SQLException(); + } + return null; + } + + /** {@inheritDoc} */ + @Override + public Map> getTypeMap() throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public void setTypeMap(Map> map) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setHoldability(int holdability) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public int getHoldability() throws SQLException + { + return (int) foo; + } + + /** {@inheritDoc} */ + @Override + public Savepoint setSavepoint() throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Savepoint setSavepoint(String name) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public void rollback(Savepoint savepoint) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void releaseSavepoint(Savepoint savepoint) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException + { + if (throwException) { + throw new SQLException(); + } + return null; + } + + /** {@inheritDoc} */ + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException + { + if (throwException) { + throw new SQLException(); + } + return new StubPreparedStatement(this); + } + + /** {@inheritDoc} */ + @Override + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException + { + if (throwException) { + throw new SQLException(); + } + return null; + } + + /** {@inheritDoc} */ + @Override + public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException + { + if (throwException) { + throw new SQLException(); + } + return new StubPreparedStatement(this); + } + + /** {@inheritDoc} */ + @Override + public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException + { + if (throwException) { + throw new SQLException(); + } + return new StubPreparedStatement(this); + } + + /** {@inheritDoc} */ + @Override + public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException + { + if (throwException) { + throw new SQLException(); + } + return new StubPreparedStatement(this); + } + + /** {@inheritDoc} */ + @Override + public Clob createClob() throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Blob createBlob() throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public NClob createNClob() throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public SQLXML createSQLXML() throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public boolean isValid(int timeout) throws SQLException + { + if (throwException) { + throw new SQLException(); + } + return true; + } + + /** {@inheritDoc} */ + @Override + public void setClientInfo(String name, String value) throws SQLClientInfoException + { + } + + /** {@inheritDoc} */ + @Override + public void setClientInfo(Properties properties) throws SQLClientInfoException + { + } + + /** {@inheritDoc} */ + @Override + public String getClientInfo(String name) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Properties getClientInfo() throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Array createArrayOf(String typeName, Object[] elements) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Struct createStruct(String typeName, Object[] attributes) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + public void setSchema(String schema) throws SQLException + { + } + + /** {@inheritDoc} */ + public String getSchema() throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + public void abort(Executor executor) throws SQLException + { + throw new SQLException("Intentional exception during abort"); + } + + /** {@inheritDoc} */ + public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException + { + } + + /** {@inheritDoc} */ + public int getNetworkTimeout() throws SQLException + { + if (oldDriver) { + throw new AbstractMethodError(); + } + + return 0; + } + +} diff --git a/src/test/java/com/zaxxer/hikari/mocks/StubDataSource.java b/src/test/java/com/zaxxer/hikari/mocks/StubDataSource.java new file mode 100755 index 0000000..c7da189 --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/mocks/StubDataSource.java @@ -0,0 +1,149 @@ +/* + * 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.mocks; + +import com.zaxxer.hikari.util.UtilityElf; + +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.logging.Logger; + +import javax.sql.DataSource; + +/** + * + * @author Brett Wooldridge + */ +public class StubDataSource implements DataSource +{ + private String user; + private String password; + private PrintWriter logWriter; + private SQLException throwException; + private long connectionAcquistionTime = 0; + private int loginTimeout; + + public String getUser() + { + return user; + } + + public void setUser(String user) + { + this.user = user; + } + + public String getPassword() + { + return password; + } + + public void setPassword(String password) + { + this.password = password; + } + + public void setURL(String url) + { + // we don't care + } + + /** {@inheritDoc} */ + @Override + public PrintWriter getLogWriter() throws SQLException + { + return logWriter; + } + + /** {@inheritDoc} */ + @Override + public void setLogWriter(PrintWriter out) throws SQLException + { + this.logWriter = out; + } + + /** {@inheritDoc} */ + @Override + public void setLoginTimeout(int seconds) throws SQLException + { + this.loginTimeout = seconds; + } + + /** {@inheritDoc} */ + @Override + public int getLoginTimeout() throws SQLException + { + return loginTimeout; + } + + /** {@inheritDoc} */ + public Logger getParentLogger() throws SQLFeatureNotSupportedException + { + return null; + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override + public T unwrap(Class iface) throws SQLException + { + if (iface.isInstance(this)) { + return (T) this; + } + + throw new SQLException("Wrapped DataSource is not an instance of " + iface); + } + + /** {@inheritDoc} */ + @Override + public boolean isWrapperFor(Class iface) throws SQLException + { + return false; + } + + /** {@inheritDoc} */ + @Override + public Connection getConnection() throws SQLException + { + if (throwException != null) { + throw throwException; + } + if (connectionAcquistionTime > 0) { + UtilityElf.quietlySleep(connectionAcquistionTime); + } + + return new StubConnection(); + } + + /** {@inheritDoc} */ + @Override + public Connection getConnection(String username, String password) throws SQLException + { + return new StubConnection(); + } + + public void setThrowException(SQLException e) + { + this.throwException = e; + } + + public void setConnectionAcquistionTime(long connectionAcquisitionTime) { + this.connectionAcquistionTime = connectionAcquisitionTime; + } +} diff --git a/src/test/java/com/zaxxer/hikari/mocks/StubDriver.java b/src/test/java/com/zaxxer/hikari/mocks/StubDriver.java new file mode 100644 index 0000000..eff7292 --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/mocks/StubDriver.java @@ -0,0 +1,96 @@ +/* + * 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.mocks; + +import java.sql.Connection; +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.DriverPropertyInfo; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.Properties; +import java.util.logging.Logger; + +/** + * + * @author Brett Wooldridge + */ +public class StubDriver implements Driver +{ + private static final Driver driver; + + static + { + driver = new StubDriver(); + try + { + DriverManager.registerDriver(driver); + } + catch (SQLException e) + { + e.printStackTrace(); + } + } + + /** {@inheritDoc} */ + @Override + public Connection connect(String url, Properties info) throws SQLException + { + return new StubConnection(); + } + + /** {@inheritDoc} */ + @Override + public boolean acceptsURL(String url) throws SQLException + { + return "jdbc:stub".equals(url); + } + + /** {@inheritDoc} */ + @Override + public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public int getMajorVersion() + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public int getMinorVersion() + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public boolean jdbcCompliant() + { + return true; + } + + /** {@inheritDoc} */ + public Logger getParentLogger() throws SQLFeatureNotSupportedException + { + return null; + } +} diff --git a/src/test/java/com/zaxxer/hikari/mocks/StubPreparedStatement.java b/src/test/java/com/zaxxer/hikari/mocks/StubPreparedStatement.java new file mode 100644 index 0000000..5c48f85 --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/mocks/StubPreparedStatement.java @@ -0,0 +1,671 @@ +/* + * 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.mocks; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.Date; +import java.sql.NClob; +import java.sql.ParameterMetaData; +import java.sql.PreparedStatement; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Calendar; + +/** + * + * @author Brett Wooldridge + */ +public class StubPreparedStatement extends StubStatement implements PreparedStatement +{ + public StubPreparedStatement(Connection connection) + { + super(connection); + } + + /** {@inheritDoc} */ + @Override + public ResultSet executeQuery(String sql) throws SQLException + { + return new StubResultSet(); + } + + /** {@inheritDoc} */ + @Override + public int executeUpdate(String sql) throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public int getMaxFieldSize() throws SQLException + { + throw new SQLException("Simulated disconnection error", "08999"); + } + + /** {@inheritDoc} */ + @Override + public void setMaxFieldSize(int max) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public int getMaxRows() throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public void setMaxRows(int max) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setEscapeProcessing(boolean enable) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public int getQueryTimeout() throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public void setQueryTimeout(int seconds) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void cancel() throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public SQLWarning getWarnings() throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public void clearWarnings() throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setCursorName(String name) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public boolean execute(String sql) throws SQLException + { + return false; + } + + /** {@inheritDoc} */ + @Override + public ResultSet getResultSet() throws SQLException + { + return new StubResultSet(); + } + + /** {@inheritDoc} */ + @Override + public int getUpdateCount() throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public boolean getMoreResults() throws SQLException + { + if (isClosed()) + { + throw new SQLException("Connection is closed"); + } + return false; + } + + /** {@inheritDoc} */ + @Override + public void setFetchDirection(int direction) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public int getFetchDirection() throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public void setFetchSize(int rows) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public int getFetchSize() throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public int getResultSetConcurrency() throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public int getResultSetType() throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public void addBatch(String sql) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void clearBatch() throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public int[] executeBatch() throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public boolean getMoreResults(int current) throws SQLException + { + return false; + } + + /** {@inheritDoc} */ + @Override + public ResultSet getGeneratedKeys() throws SQLException + { + return new StubResultSet(); + } + + /** {@inheritDoc} */ + @Override + public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public int executeUpdate(String sql, int[] columnIndexes) throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public int executeUpdate(String sql, String[] columnNames) throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public boolean execute(String sql, int autoGeneratedKeys) throws SQLException + { + return false; + } + + /** {@inheritDoc} */ + @Override + public boolean execute(String sql, int[] columnIndexes) throws SQLException + { + return false; + } + + /** {@inheritDoc} */ + @Override + public boolean execute(String sql, String[] columnNames) throws SQLException + { + return false; + } + + /** {@inheritDoc} */ + @Override + public int getResultSetHoldability() throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public void setPoolable(boolean poolable) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public boolean isPoolable() throws SQLException + { + return false; + } + + /** {@inheritDoc} */ + @Override + public void closeOnCompletion() throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public boolean isCloseOnCompletion() throws SQLException + { + return false; + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override + public T unwrap(Class iface) throws SQLException + { + if (iface.isInstance(this)) { + return (T) this; + } + + throw new SQLException("Wrapped connection is not an instance of " + iface); + } + + /** {@inheritDoc} */ + @Override + public boolean isWrapperFor(Class iface) throws SQLException + { + return false; + } + + /** {@inheritDoc} */ + @Override + public ResultSet executeQuery() throws SQLException + { + return new StubResultSet(); + } + + /** {@inheritDoc} */ + @Override + public int executeUpdate() throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public void setNull(int parameterIndex, int sqlType) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setBoolean(int parameterIndex, boolean x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setByte(int parameterIndex, byte x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setShort(int parameterIndex, short x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setInt(int parameterIndex, int x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setLong(int parameterIndex, long x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setFloat(int parameterIndex, float x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setDouble(int parameterIndex, double x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setString(int parameterIndex, String x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setBytes(int parameterIndex, byte[] x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setDate(int parameterIndex, Date x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setTime(int parameterIndex, Time x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void clearParameters() throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setObject(int parameterIndex, Object x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public boolean execute() throws SQLException + { + return false; + } + + /** {@inheritDoc} */ + @Override + public void addBatch() throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setRef(int parameterIndex, Ref x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setBlob(int parameterIndex, Blob x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setClob(int parameterIndex, Clob x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setArray(int parameterIndex, Array x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public ResultSetMetaData getMetaData() throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setURL(int parameterIndex, URL x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public ParameterMetaData getParameterMetaData() throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public void setRowId(int parameterIndex, RowId x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setNString(int parameterIndex, String value) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setNClob(int parameterIndex, NClob value) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setClob(int parameterIndex, Reader reader, long length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setClob(int parameterIndex, Reader reader) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void setNClob(int parameterIndex, Reader reader) throws SQLException + { + } + +} diff --git a/src/test/java/com/zaxxer/hikari/mocks/StubResultSet.java b/src/test/java/com/zaxxer/hikari/mocks/StubResultSet.java new file mode 100644 index 0000000..911daba --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/mocks/StubResultSet.java @@ -0,0 +1,1292 @@ +/* + * 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.mocks; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Date; +import java.sql.NClob; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Calendar; +import java.util.Map; + +/** + * + * @author Brett Wooldridge + */ +public class StubResultSet implements ResultSet +{ + private int counter; + private boolean closed; + + /** {@inheritDoc} */ + @Override + public T unwrap(Class iface) throws SQLException + { + + return null; + } + + /** {@inheritDoc} */ + @Override + public boolean isWrapperFor(Class iface) throws SQLException + { + return false; + } + + /** {@inheritDoc} */ + @Override + public boolean next() throws SQLException + { + return (counter > 100000); + } + + /** {@inheritDoc} */ + @Override + public void close() throws SQLException + { + closed = true; + } + + /** {@inheritDoc} */ + @Override + public boolean wasNull() throws SQLException + { + return false; + } + + /** {@inheritDoc} */ + @Override + public String getString(int columnIndex) throws SQLException + { + return "aString"; + } + + /** {@inheritDoc} */ + @Override + public boolean getBoolean(int columnIndex) throws SQLException + { + return false; + } + + /** {@inheritDoc} */ + @Override + public byte getByte(int columnIndex) throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public short getShort(int columnIndex) throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public int getInt(int columnIndex) throws SQLException + { + return ++counter; + } + + /** {@inheritDoc} */ + @Override + public long getLong(int columnIndex) throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public float getFloat(int columnIndex) throws SQLException + { + throw new SQLException("Simulated disconnection error", "08999"); + } + + /** {@inheritDoc} */ + @Override + public double getDouble(int columnIndex) throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public byte[] getBytes(int columnIndex) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Date getDate(int columnIndex) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Time getTime(int columnIndex) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Timestamp getTimestamp(int columnIndex) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public InputStream getAsciiStream(int columnIndex) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public InputStream getUnicodeStream(int columnIndex) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public InputStream getBinaryStream(int columnIndex) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public String getString(String columnLabel) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public boolean getBoolean(String columnLabel) throws SQLException + { + return false; + } + + /** {@inheritDoc} */ + @Override + public byte getByte(String columnLabel) throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public short getShort(String columnLabel) throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public int getInt(String columnLabel) throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public long getLong(String columnLabel) throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public float getFloat(String columnLabel) throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public double getDouble(String columnLabel) throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public BigDecimal getBigDecimal(String columnLabel, int scale) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public byte[] getBytes(String columnLabel) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Date getDate(String columnLabel) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Time getTime(String columnLabel) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Timestamp getTimestamp(String columnLabel) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public InputStream getAsciiStream(String columnLabel) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public InputStream getUnicodeStream(String columnLabel) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public InputStream getBinaryStream(String columnLabel) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public SQLWarning getWarnings() throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public void clearWarnings() throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public String getCursorName() throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public ResultSetMetaData getMetaData() throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Object getObject(int columnIndex) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Object getObject(String columnLabel) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public int findColumn(String columnLabel) throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public Reader getCharacterStream(int columnIndex) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Reader getCharacterStream(String columnLabel) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public BigDecimal getBigDecimal(int columnIndex) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public BigDecimal getBigDecimal(String columnLabel) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public boolean isBeforeFirst() throws SQLException + { + return false; + } + + /** {@inheritDoc} */ + @Override + public boolean isAfterLast() throws SQLException + { + return false; + } + + /** {@inheritDoc} */ + @Override + public boolean isFirst() throws SQLException + { + return false; + } + + /** {@inheritDoc} */ + @Override + public boolean isLast() throws SQLException + { + return false; + } + + /** {@inheritDoc} */ + @Override + public void beforeFirst() throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void afterLast() throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public boolean first() throws SQLException + { + return false; + } + + /** {@inheritDoc} */ + @Override + public boolean last() throws SQLException + { + return false; + } + + /** {@inheritDoc} */ + @Override + public int getRow() throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public boolean absolute(int row) throws SQLException + { + return false; + } + + /** {@inheritDoc} */ + @Override + public boolean relative(int rows) throws SQLException + { + return false; + } + + /** {@inheritDoc} */ + @Override + public boolean previous() throws SQLException + { + return false; + } + + /** {@inheritDoc} */ + @Override + public void setFetchDirection(int direction) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public int getFetchDirection() throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public void setFetchSize(int rows) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public int getFetchSize() throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public int getType() throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public int getConcurrency() throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public boolean rowUpdated() throws SQLException + { + return false; + } + + /** {@inheritDoc} */ + @Override + public boolean rowInserted() throws SQLException + { + return false; + } + + /** {@inheritDoc} */ + @Override + public boolean rowDeleted() throws SQLException + { + return false; + } + + /** {@inheritDoc} */ + @Override + public void updateNull(int columnIndex) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateBoolean(int columnIndex, boolean x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateByte(int columnIndex, byte x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateShort(int columnIndex, short x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateInt(int columnIndex, int x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateLong(int columnIndex, long x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateFloat(int columnIndex, float x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateDouble(int columnIndex, double x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateBigDecimal(int columnIndex, BigDecimal x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateString(int columnIndex, String x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateBytes(int columnIndex, byte[] x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateDate(int columnIndex, Date x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateTime(int columnIndex, Time x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateTimestamp(int columnIndex, Timestamp x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateAsciiStream(int columnIndex, InputStream x, int length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateBinaryStream(int columnIndex, InputStream x, int length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateCharacterStream(int columnIndex, Reader x, int length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateObject(int columnIndex, Object x, int scaleOrLength) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateObject(int columnIndex, Object x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateNull(String columnLabel) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateBoolean(String columnLabel, boolean x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateByte(String columnLabel, byte x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateShort(String columnLabel, short x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateInt(String columnLabel, int x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateLong(String columnLabel, long x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateFloat(String columnLabel, float x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateDouble(String columnLabel, double x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateBigDecimal(String columnLabel, BigDecimal x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateString(String columnLabel, String x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateBytes(String columnLabel, byte[] x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateDate(String columnLabel, Date x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateTime(String columnLabel, Time x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateTimestamp(String columnLabel, Timestamp x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateAsciiStream(String columnLabel, InputStream x, int length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateBinaryStream(String columnLabel, InputStream x, int length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateCharacterStream(String columnLabel, Reader reader, int length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateObject(String columnLabel, Object x, int scaleOrLength) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateObject(String columnLabel, Object x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void insertRow() throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateRow() throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void deleteRow() throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void refreshRow() throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void cancelRowUpdates() throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void moveToInsertRow() throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void moveToCurrentRow() throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public Statement getStatement() throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Object getObject(int columnIndex, Map> map) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Ref getRef(int columnIndex) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Blob getBlob(int columnIndex) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Clob getClob(int columnIndex) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Array getArray(int columnIndex) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Object getObject(String columnLabel, Map> map) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Ref getRef(String columnLabel) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Blob getBlob(String columnLabel) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Clob getClob(String columnLabel) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Array getArray(String columnLabel) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Date getDate(int columnIndex, Calendar cal) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Date getDate(String columnLabel, Calendar cal) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Time getTime(int columnIndex, Calendar cal) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Time getTime(String columnLabel, Calendar cal) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public URL getURL(int columnIndex) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public URL getURL(String columnLabel) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public void updateRef(int columnIndex, Ref x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateRef(String columnLabel, Ref x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateBlob(int columnIndex, Blob x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateBlob(String columnLabel, Blob x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateClob(int columnIndex, Clob x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateClob(String columnLabel, Clob x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateArray(int columnIndex, Array x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateArray(String columnLabel, Array x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public RowId getRowId(int columnIndex) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public RowId getRowId(String columnLabel) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public void updateRowId(int columnIndex, RowId x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateRowId(String columnLabel, RowId x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public int getHoldability() throws SQLException + { + return 0; + } + + /** {@inheritDoc} */ + @Override + public boolean isClosed() throws SQLException + { + return closed; + } + + /** {@inheritDoc} */ + @Override + public void updateNString(int columnIndex, String nString) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateNString(String columnLabel, String nString) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateNClob(int columnIndex, NClob nClob) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateNClob(String columnLabel, NClob nClob) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public NClob getNClob(int columnIndex) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public NClob getNClob(String columnLabel) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public SQLXML getSQLXML(int columnIndex) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public SQLXML getSQLXML(String columnLabel) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public void updateSQLXML(int columnIndex, SQLXML xmlObject) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public String getNString(int columnIndex) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public String getNString(String columnLabel) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Reader getNCharacterStream(int columnIndex) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public Reader getNCharacterStream(String columnLabel) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + @Override + public void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateNCharacterStream(String columnLabel, Reader reader, long length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateAsciiStream(String columnLabel, InputStream x, long length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateBinaryStream(String columnLabel, InputStream x, long length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateCharacterStream(String columnLabel, Reader reader, long length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateBlob(int columnIndex, InputStream inputStream, long length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateBlob(String columnLabel, InputStream inputStream, long length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateClob(int columnIndex, Reader reader, long length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateClob(String columnLabel, Reader reader, long length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateNClob(int columnIndex, Reader reader, long length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateNClob(String columnLabel, Reader reader, long length) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateNCharacterStream(String columnLabel, Reader reader) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateAsciiStream(int columnIndex, InputStream x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateCharacterStream(int columnIndex, Reader x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateAsciiStream(String columnLabel, InputStream x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateCharacterStream(String columnLabel, Reader reader) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateBlob(int columnIndex, InputStream inputStream) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateBlob(String columnLabel, InputStream inputStream) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateClob(int columnIndex, Reader reader) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateClob(String columnLabel, Reader reader) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateNClob(int columnIndex, Reader reader) throws SQLException + { + } + + /** {@inheritDoc} */ + @Override + public void updateNClob(String columnLabel, Reader reader) throws SQLException + { + } + + /** {@inheritDoc} */ + public T getObject(int columnIndex, Class type) throws SQLException + { + return null; + } + + /** {@inheritDoc} */ + public T getObject(String columnLabel, Class type) throws SQLException + { + return null; + } + +} diff --git a/src/test/java/com/zaxxer/hikari/mocks/StubStatement.java b/src/test/java/com/zaxxer/hikari/mocks/StubStatement.java new file mode 100644 index 0000000..73fec69 --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/mocks/StubStatement.java @@ -0,0 +1,387 @@ +/* + * 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.mocks; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.SQLWarning; +import java.sql.Statement; + +/** + * + * @author Brett Wooldridge + */ +public class StubStatement implements Statement +{ + public static volatile boolean oldDriver; + private boolean closed; + private Connection connection; + + public StubStatement(Connection connection) { + this.connection = connection; + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override + public T unwrap(Class iface) throws SQLException + { + checkClosed(); + return (T) this; + } + + /** {@inheritDoc} */ + @Override + public boolean isWrapperFor(Class iface) throws SQLException + { + checkClosed(); + return false; + } + + /** {@inheritDoc} */ + @Override + public ResultSet executeQuery(String sql) throws SQLException + { + checkClosed(); + StubResultSet resultSet = new StubResultSet(); + return resultSet; + } + + /** {@inheritDoc} */ + @Override + public int executeUpdate(String sql) throws SQLException + { + checkClosed(); + return 0; + } + + /** {@inheritDoc} */ + @Override + public void close() throws SQLException + { + closed = true; + } + + /** {@inheritDoc} */ + @Override + public int getMaxFieldSize() throws SQLException + { + checkClosed(); + return 0; + } + + /** {@inheritDoc} */ + @Override + public void setMaxFieldSize(int max) throws SQLException + { + checkClosed(); + } + + /** {@inheritDoc} */ + @Override + public int getMaxRows() throws SQLException + { + checkClosed(); + return 0; + } + + /** {@inheritDoc} */ + @Override + public void setMaxRows(int max) throws SQLException + { + checkClosed(); + } + + /** {@inheritDoc} */ + @Override + public void setEscapeProcessing(boolean enable) throws SQLException + { + checkClosed(); + } + + /** {@inheritDoc} */ + @Override + public int getQueryTimeout() throws SQLException + { + checkClosed(); + return 0; + } + + /** {@inheritDoc} */ + @Override + public void setQueryTimeout(int seconds) throws SQLException + { + if (oldDriver) { + throw new SQLFeatureNotSupportedException(); + } + + checkClosed(); + } + + /** {@inheritDoc} */ + @Override + public void cancel() throws SQLException + { + checkClosed(); + } + + /** {@inheritDoc} */ + @Override + public SQLWarning getWarnings() throws SQLException + { + checkClosed(); + return null; + } + + /** {@inheritDoc} */ + @Override + public void clearWarnings() throws SQLException + { + checkClosed(); + } + + /** {@inheritDoc} */ + @Override + public void setCursorName(String name) throws SQLException + { + checkClosed(); + } + + /** {@inheritDoc} */ + @Override + public boolean execute(String sql) throws SQLException + { + checkClosed(); + return false; + } + + /** {@inheritDoc} */ + @Override + public ResultSet getResultSet() throws SQLException + { + checkClosed(); + return new StubResultSet(); + } + + /** {@inheritDoc} */ + @Override + public int getUpdateCount() throws SQLException + { + checkClosed(); + return 0; + } + + /** {@inheritDoc} */ + @Override + public boolean getMoreResults() throws SQLException + { + checkClosed(); + return false; + } + + /** {@inheritDoc} */ + @Override + public void setFetchDirection(int direction) throws SQLException + { + checkClosed(); + } + + /** {@inheritDoc} */ + @Override + public int getFetchDirection() throws SQLException + { + checkClosed(); + return 0; + } + + /** {@inheritDoc} */ + @Override + public void setFetchSize(int rows) throws SQLException + { + checkClosed(); + } + + /** {@inheritDoc} */ + @Override + public int getFetchSize() throws SQLException + { + checkClosed(); + return 0; + } + + /** {@inheritDoc} */ + @Override + public int getResultSetConcurrency() throws SQLException + { + checkClosed(); + return 0; + } + + /** {@inheritDoc} */ + @Override + public int getResultSetType() throws SQLException + { + checkClosed(); + return 0; + } + + /** {@inheritDoc} */ + @Override + public void addBatch(String sql) throws SQLException + { + checkClosed(); + } + + /** {@inheritDoc} */ + @Override + public void clearBatch() throws SQLException + { + checkClosed(); + } + + /** {@inheritDoc} */ + @Override + public int[] executeBatch() throws SQLException + { + checkClosed(); + return null; + } + + /** {@inheritDoc} */ + @Override + public Connection getConnection() throws SQLException + { + checkClosed(); + return connection; + } + + /** {@inheritDoc} */ + @Override + public boolean getMoreResults(int current) throws SQLException + { + checkClosed(); + return false; + } + + /** {@inheritDoc} */ + @Override + public ResultSet getGeneratedKeys() throws SQLException + { + checkClosed(); + return new StubResultSet(); + } + + /** {@inheritDoc} */ + @Override + public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException + { + checkClosed(); + return 0; + } + + /** {@inheritDoc} */ + @Override + public int executeUpdate(String sql, int[] columnIndexes) throws SQLException + { + checkClosed(); + return 0; + } + + /** {@inheritDoc} */ + @Override + public int executeUpdate(String sql, String[] columnNames) throws SQLException + { + checkClosed(); + return 0; + } + + /** {@inheritDoc} */ + @Override + public boolean execute(String sql, int autoGeneratedKeys) throws SQLException + { + checkClosed(); + return false; + } + + /** {@inheritDoc} */ + @Override + public boolean execute(String sql, int[] columnIndexes) throws SQLException + { + checkClosed(); + return false; + } + + /** {@inheritDoc} */ + @Override + public boolean execute(String sql, String[] columnNames) throws SQLException + { + checkClosed(); + return false; + } + + /** {@inheritDoc} */ + @Override + public int getResultSetHoldability() throws SQLException + { + checkClosed(); + return 0; + } + + /** {@inheritDoc} */ + @Override + public boolean isClosed() throws SQLException + { + return closed; + } + + /** {@inheritDoc} */ + @Override + public void setPoolable(boolean poolable) throws SQLException + { + checkClosed(); + } + + /** {@inheritDoc} */ + @Override + public boolean isPoolable() throws SQLException + { + checkClosed(); + return false; + } + + /** {@inheritDoc} */ + public void closeOnCompletion() throws SQLException + { + checkClosed(); + } + + /** {@inheritDoc} */ + public boolean isCloseOnCompletion() throws SQLException + { + checkClosed(); + return false; + } + + private void checkClosed() throws SQLException + { + if (closed) { + throw new SQLException("Statement is closed"); + } + } +} diff --git a/src/test/java/com/zaxxer/hikari/osgi/OSGiBundleTest.java b/src/test/java/com/zaxxer/hikari/osgi/OSGiBundleTest.java new file mode 100644 index 0000000..761a802 --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/osgi/OSGiBundleTest.java @@ -0,0 +1,91 @@ +/* + * 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.osgi; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.ops4j.pax.exam.Configuration; +import org.ops4j.pax.exam.Option; +import org.ops4j.pax.exam.junit.PaxExam; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; + +import javax.inject.Inject; + +import java.io.File; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.ops4j.pax.exam.CoreOptions.*; + +/** + * @author lburgazzoli + */ +@RunWith(PaxExam.class) +public class OSGiBundleTest +{ + @Inject + BundleContext context; + + @Configuration + public Option[] config() + { + return options( + systemProperty("org.osgi.framework.storage.clean").value("true"), + systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value("WARN"), + mavenBundle("org.slf4j","slf4j-api","1.7.5"), + mavenBundle("org.slf4j","slf4j-simple","1.7.5").noStart(), + mavenBundle("org.javassist", "javassist", "3.19.0-GA"), + new File("target/classes").exists() + ? bundle("reference:file:target/classes") + : bundle("reference:file:../target/classes"), + junitBundles(), + cleanCaches() + ); + } + + @Test + public void checkInject() + { + assertNotNull(context); + } + + @Test + public void checkBundle() + { + Boolean bundleFound = false; + Boolean bundleActive = false; + + Bundle[] bundles = context.getBundles(); + for(Bundle bundle : bundles) + { + if(bundle != null) + { + if(bundle.getSymbolicName().equals("com.zaxxer.HikariCP")) + { + bundleFound = true; + if(bundle.getState() == Bundle.ACTIVE) + { + bundleActive = true; + } + } + } + } + + assertTrue(bundleFound); + assertTrue(bundleActive); + } +} 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> futures = new ArrayList<>(); + + for (int i = 0; i < 500; i++) { + final PreparedStatement preparedStatement = + connection.prepareStatement(""); + + futures.add(executorService.submit(new Callable() { + + @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 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 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() { + /** {@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 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 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() { + @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 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 getMultiPool(HikariDataSource ds) + { + try { + Field field = ds.getClass().getDeclaredField("multiPool"); + field.setAccessible(true); + return (HashMap) 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 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 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")); + } + } + } +} diff --git a/src/test/java/com/zaxxer/hikari/util/ClockSourceTest.java b/src/test/java/com/zaxxer/hikari/util/ClockSourceTest.java new file mode 100644 index 0000000..2d0482a --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/util/ClockSourceTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2016 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.util; + +import org.junit.Assert; +import org.junit.Test; + +import static java.util.concurrent.TimeUnit.DAYS; +import static java.util.concurrent.TimeUnit.HOURS; +import static java.util.concurrent.TimeUnit.MICROSECONDS; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; + +/** + * + * @author Brett Wooldridge + */ +public class ClockSourceTest +{ + @Test + public void testClockSourceDisplay() + { + ClockSource msSource = new ClockSource.MillisecondClockSource(); + + final long sTime = DAYS.toMillis(3) + HOURS.toMillis(9) + MINUTES.toMillis(24) + SECONDS.toMillis(18) + MILLISECONDS.toMillis(572); + + final long eTime = DAYS.toMillis(4) + HOURS.toMillis(9) + MINUTES.toMillis(55) + SECONDS.toMillis(23) + MILLISECONDS.toMillis(777); + String ds1 = msSource.elapsedDisplayString0(sTime, eTime); + Assert.assertEquals("1d31m5s205ms", ds1); + + final long eTime2 = DAYS.toMillis(3) + HOURS.toMillis(8) + MINUTES.toMillis(24) + SECONDS.toMillis(23) + MILLISECONDS.toMillis(777); + String ds2 = msSource.elapsedDisplayString0(sTime, eTime2); + Assert.assertEquals("-59m54s795ms", ds2); + + + ClockSource nsSource = new ClockSource.NanosecondClockSource(); + + final long sTime2 = DAYS.toNanos(3) + HOURS.toNanos(9) + MINUTES.toNanos(24) + SECONDS.toNanos(18) + MILLISECONDS.toNanos(572) + MICROSECONDS.toNanos(324) + NANOSECONDS.toNanos(823); + + final long eTime3 = DAYS.toNanos(4) + HOURS.toNanos(19) + MINUTES.toNanos(55) + SECONDS.toNanos(23) + MILLISECONDS.toNanos(777) + MICROSECONDS.toNanos(0) + NANOSECONDS.toNanos(982); + String ds3 = nsSource.elapsedDisplayString0(sTime2, eTime3); + Assert.assertEquals("1d10h31m5s204ms676µs159ns", ds3); + } +} diff --git a/src/test/java/com/zaxxer/hikari/util/TestFastList.java b/src/test/java/com/zaxxer/hikari/util/TestFastList.java new file mode 100644 index 0000000..089ff5a --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/util/TestFastList.java @@ -0,0 +1,172 @@ +/* + * 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.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; + +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Iterator; + +import org.junit.Test; + +import com.zaxxer.hikari.mocks.StubStatement; + +public class TestFastList +{ + @Test + public void testAddRemove() + { + ArrayList verifyList = new ArrayList<>(); + + FastList list = new FastList<>(Statement.class); + for (int i = 0; i < 32; i++) + { + StubStatement statement = new StubStatement(null); + list.add(statement); + verifyList.add(statement); + } + + for (int i = 0; i < 32; i++) + { + assertNotNull("Element " + i + " was null but should be " + verifyList.get(i), list.get(0)); + int size = list.size(); + list.remove(verifyList.get(i)); + assertSame(size - 1, list.size()); + } + } + + @Test + public void testAddRemoveTail() + { + ArrayList verifyList = new ArrayList<>(); + + FastList list = new FastList<>(Statement.class); + for (int i = 0; i < 32; i++) + { + StubStatement statement = new StubStatement(null); + list.add(statement); + verifyList.add(statement); + } + + for (int i = 31; i >= 0; i--) + { + assertNotNull("Element " + i, list.get(i)); + int size = list.size(); + list.remove(verifyList.get(i)); + assertSame(size - 1, list.size()); + } + } + + @Test + public void testOverflow() + { + ArrayList verifyList = new ArrayList<>(); + + FastList list = new FastList<>(Statement.class); + for (int i = 0; i < 100; i++) + { + StubStatement statement = new StubStatement(null); + list.add(statement); + verifyList.add(statement); + } + + for (int i = 0; i < 100; i++) + { + assertNotNull("Element " + i, list.get(i)); + assertSame(verifyList.get(i), list.get(i)); + } + } + + @Test + public void testIterator() + { + FastList list = new FastList<>(Statement.class); + for (int i = 0; i < 100; i++) + { + StubStatement statement = new StubStatement(null); + list.add(statement); + } + + Iterator iter = list.iterator(); + for (int i = 0; i < list.size(); i++) { + assertSame(list.get(i), iter.next()); + } + } + + @Test + public void testClear() + { + FastList list = new FastList<>(Statement.class); + for (int i = 0; i < 100; i++) + { + StubStatement statement = new StubStatement(null); + list.add(statement); + } + + assertNotEquals(0, list.size()); + list.clear(); + assertEquals(0, list.size()); + } + + @Test + public void testRemoveLast() + { + FastList list = new FastList<>(Statement.class); + + Statement last = null; + for (int i = 0; i < 100; i++) + { + StubStatement statement = new StubStatement(null); + list.add(statement); + last = statement; + } + + assertEquals(last, list.removeLast()); + assertEquals(99, list.size()); + } + + @Test + public void testPolyMorphism1() + { + class Foo implements Base2 { + + } + + class Bar extends Foo { + + } + + FastList list = new FastList<>(Base.class, 2); + list.add(new Foo()); + list.add(new Foo()); + list.add(new Bar()); + } + + interface Base + { + + } + + interface Base2 extends Base + { + + } +} diff --git a/src/test/java/com/zaxxer/hikari/util/TomcatConcurrentBagLeakTest.java b/src/test/java/com/zaxxer/hikari/util/TomcatConcurrentBagLeakTest.java new file mode 100644 index 0000000..06957b2 --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/util/TomcatConcurrentBagLeakTest.java @@ -0,0 +1,328 @@ +/* + * Copyright (C) 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.util; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.io.DataInputStream; +import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URL; +import java.util.concurrent.CompletableFuture; + +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry; + +/** + * @author Brett Wooldridge + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TomcatConcurrentBagLeakTest +{ + @Test + public void testConcurrentBagForLeaks() throws Exception + { + ClassLoader cl = new FauxWebClassLoader(); + Class clazz = cl.loadClass(this.getClass().getName() + "$FauxWebContext"); + Object fauxWebContext = clazz.newInstance(); + + Method createConcurrentBag = clazz.getDeclaredMethod("createConcurrentBag"); + createConcurrentBag.invoke(fauxWebContext); + + Field failureException = clazz.getDeclaredField("failureException"); + Exception ex = (Exception) failureException.get(fauxWebContext); + assertNull(ex); + } + + @Test + public void testConcurrentBagForLeaks2() throws Exception + { + ClassLoader cl = this.getClass().getClassLoader(); + Class clazz = cl.loadClass(this.getClass().getName() + "$FauxWebContext"); + Object fauxWebContext = clazz.newInstance(); + + Method createConcurrentBag = clazz.getDeclaredMethod("createConcurrentBag"); + createConcurrentBag.invoke(fauxWebContext); + + Field failureException = clazz.getDeclaredField("failureException"); + Exception ex = (Exception) failureException.get(fauxWebContext); + assertNotNull(ex); + } + + static class FauxWebClassLoader extends ClassLoader + { + static final byte[] classBytes = new byte[16_000]; + + @Override + public Class loadClass(String name) throws ClassNotFoundException + { + if (name.startsWith("java") || name.startsWith("org")) { + return super.loadClass(name, true); + } + + final String resourceName = "/" + name.replace('.', '/') + ".class"; + final URL resource = this.getClass().getResource(resourceName); + try (DataInputStream is = new DataInputStream(resource.openStream())) { + int read = 0; + while (read < classBytes.length) { + final int rc = is.read(classBytes, read, classBytes.length - read); + if (rc == -1) { + break; + } + read += rc; + } + + return defineClass(name, classBytes, 0, read); + } + catch (IOException e) { + throw new ClassNotFoundException(name); + } + } + } + + public static class PoolEntry implements IConcurrentBagEntry + { + private int state; + + @Override + public boolean compareAndSet(int expectState, int newState) + { + this.state = newState; + return true; + } + + @Override + public void setState(int newState) + { + this.state = newState; + } + + @Override + public int getState() + { + return state; + } + } + + public static class FauxWebContext + { + private static final Logger log = LoggerFactory.getLogger(FauxWebContext.class); + + public Exception failureException; + + public void createConcurrentBag() throws InterruptedException + { + try (ConcurrentBag bag = new ConcurrentBag<>((x) -> CompletableFuture.completedFuture(Boolean.TRUE))) { + + PoolEntry entry = new PoolEntry(); + bag.add(entry); + + PoolEntry borrowed = bag.borrow(100, MILLISECONDS); + bag.requite(borrowed); + + PoolEntry removed = bag.borrow(100, MILLISECONDS); + bag.remove(removed); + } + + checkThreadLocalsForLeaks(); + } + + private void checkThreadLocalsForLeaks() + { + Thread[] threads = getThreads(); + + try { + // Make the fields in the Thread class that store ThreadLocals + // accessible + Field threadLocalsField = Thread.class.getDeclaredField("threadLocals"); + threadLocalsField.setAccessible(true); + Field inheritableThreadLocalsField = Thread.class.getDeclaredField("inheritableThreadLocals"); + inheritableThreadLocalsField.setAccessible(true); + // Make the underlying array of ThreadLoad.ThreadLocalMap.Entry objects + // accessible + Class tlmClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap"); + Field tableField = tlmClass.getDeclaredField("table"); + tableField.setAccessible(true); + Method expungeStaleEntriesMethod = tlmClass.getDeclaredMethod("expungeStaleEntries"); + expungeStaleEntriesMethod.setAccessible(true); + + for (int i = 0; i < threads.length; i++) { + Object threadLocalMap; + if (threads[i] != null) { + + // Clear the first map + threadLocalMap = threadLocalsField.get(threads[i]); + if (null != threadLocalMap) { + expungeStaleEntriesMethod.invoke(threadLocalMap); + checkThreadLocalMapForLeaks(threadLocalMap, tableField); + } + + // Clear the second map + threadLocalMap = inheritableThreadLocalsField.get(threads[i]); + if (null != threadLocalMap) { + expungeStaleEntriesMethod.invoke(threadLocalMap); + checkThreadLocalMapForLeaks(threadLocalMap, tableField); + } + } + } + } + catch (Throwable t) { + log.warn("Failed to check for ThreadLocal references for web application [{}]", t); + failureException = new Exception(); + } + } + + private Object getContextName() + { + return this.getClass().getName(); + } + + // THE FOLLOWING CODE COPIED FROM APACHE TOMCAT (2017/01/08) + + /** + * Analyzes the given thread local map object. Also pass in the field that + * points to the internal table to save re-calculating it on every + * call to this method. + */ + private void checkThreadLocalMapForLeaks(Object map, Field internalTableField) throws IllegalAccessException, NoSuchFieldException + { + if (map != null) { + Object[] table = (Object[]) internalTableField.get(map); + if (table != null) { + for (int j = 0; j < table.length; j++) { + Object obj = table[j]; + if (obj != null) { + boolean keyLoadedByWebapp = false; + boolean valueLoadedByWebapp = false; + // Check the key + Object key = ((Reference) obj).get(); + if (this.equals(key) || loadedByThisOrChild(key)) { + keyLoadedByWebapp = true; + } + // Check the value + Field valueField = obj.getClass().getDeclaredField("value"); + valueField.setAccessible(true); + Object value = valueField.get(obj); + if (this.equals(value) || loadedByThisOrChild(value)) { + valueLoadedByWebapp = true; + } + if (keyLoadedByWebapp || valueLoadedByWebapp) { + Object[] args = new Object[5]; + args[0] = getContextName(); + if (key != null) { + args[1] = getPrettyClassName(key.getClass()); + try { + args[2] = key.toString(); + } + catch (Exception e) { + log.warn("Unable to determine string representation of key of type [{}]", args[1], e); + args[2] = "Unknown"; + } + } + if (value != null) { + args[3] = getPrettyClassName(value.getClass()); + try { + args[4] = value.toString(); + } + catch (Exception e) { + log.warn("webappClassLoader.checkThreadLocalsForLeaks.badValue {}", args[3], e); + args[4] = "Unknown"; + } + } + + if (valueLoadedByWebapp) { + log.error("The web application [{}] created a ThreadLocal with key " + + "(value [{}]) and a value of type [{}] (value [{}]) but failed to remove " + + "it when the web application was stopped. Threads are going to be renewed " + + "over time to try and avoid a probable memory leak.", args); + failureException = new Exception(); + } + else if (value == null) { + log.debug("The web application [{}] created a ThreadLocal with key of type [{}] " + + "(value [{}]). The ThreadLocal has been correctly set to null and the " + + "key will be removed by GC.", args); + failureException = new Exception(); + } + else { + log.debug("The web application [{}] created a ThreadLocal with key of type [{}] " + + "(value [{}]) and a value of type [{}] (value [{}]). Since keys are only " + + "weakly held by the ThreadLocal Map this is not a memory leak.", args); + failureException = new Exception(); + } + } + } + } + } + } + } + + private boolean loadedByThisOrChild(Object key) + { + return key.getClass().getClassLoader() == this.getClass().getClassLoader(); + } + + /* + * Get the set of current threads as an array. + */ + private Thread[] getThreads() + { + // Get the current thread group + ThreadGroup tg = Thread.currentThread().getThreadGroup(); + // Find the root thread group + try { + while (tg.getParent() != null) { + tg = tg.getParent(); + } + } + catch (SecurityException se) { + log.warn("Unable to obtain the parent for ThreadGroup [{}]. It will not be possible to check all threads for potential memory leaks", tg.getName(), se); + } + + int threadCountGuess = tg.activeCount() + 50; + Thread[] threads = new Thread[threadCountGuess]; + int threadCountActual = tg.enumerate(threads); + // Make sure we don't miss any threads + while (threadCountActual == threadCountGuess) { + threadCountGuess *= 2; + threads = new Thread[threadCountGuess]; + // Note tg.enumerate(Thread[]) silently ignores any threads that + // can't fit into the array + threadCountActual = tg.enumerate(threads); + } + + return threads; + } + + private String getPrettyClassName(Class clazz) + { + String name = clazz.getCanonicalName(); + if (name == null) { + name = clazz.getName(); + } + return name; + } + } +} -- cgit v1.2.3