/***************************************************************** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.cayenne.datasource; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.sql.Connection; import java.sql.SQLException; import javax.sql.DataSource; import org.apache.cayenne.datasource.PoolAwareConnection; import org.apache.cayenne.datasource.UnmanagedPoolingDataSource; import org.apache.cayenne.datasource.PoolingDataSourceParameters; import org.junit.Before; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; public class PoolingDataSourceTest { private DataSource nonPooling; private PoolingDataSourceParameters params; @Before public void before() throws SQLException { nonPooling = mock(DataSource.class); when(nonPooling.getConnection()).thenAnswer(new Answer<Connection>() { @Override public Connection answer(InvocationOnMock invocation) throws Throwable { return mock(Connection.class); } }); params = new PoolingDataSourceParameters(); } @Test public void testManagePool_High() throws SQLException { int max = 5; params.setMinConnections(1); params.setMaxConnections(max); UnmanagedPoolingDataSource ds = new UnmanagedPoolingDataSource(nonPooling, params); // opening and closing 'max' connections should fill the pool to the // top... Connection[] open = new Connection[max]; for (int i = 0; i < max; i++) { open[i] = ds.getConnection(); } for (Connection c : open) { c.close(); } // now we can start calling 'managePool', and it would close connections // one at a time until we reach a threshold on idle assertEquals(max, ds.poolSize()); ds.managePool(); assertEquals(max - 1, ds.poolSize()); ds.managePool(); assertEquals(max - 2, ds.poolSize()); // pool equilibrium was reached. subsequent calls should not open or // close connections ds.managePool(); assertEquals(max - 2, ds.poolSize()); ds.managePool(); assertEquals(max - 2, ds.poolSize()); } @Test public void testManagePool_Low() throws SQLException { int min = 2; params.setMinConnections(min); params.setMaxConnections(min + 5); UnmanagedPoolingDataSource ds = new UnmanagedPoolingDataSource(nonPooling, params); // we start with a min number of connections assertEquals(min, ds.poolSize()); // now lets evict a bunch of connections before we can start growing the // pool again for (int i = 0; i < min; i++) { ds.retire(ds.uncheckNonBlocking(false)); } // now we can start calling 'managePool', and it would open connections // one at a time assertEquals(0, ds.poolSize()); ds.managePool(); assertEquals(1, ds.poolSize()); ds.managePool(); assertEquals(2, ds.poolSize()); // pool equilibrium was reached. subsequent calls should not open or // close connections ds.managePool(); assertEquals(2, ds.poolSize()); ds.managePool(); assertEquals(2, ds.poolSize()); } @Test public void testManagePool_Empty() throws SQLException { int max = 5; params.setMinConnections(1); params.setMaxConnections(max); UnmanagedPoolingDataSource ds = new UnmanagedPoolingDataSource(nonPooling, params); // opening and closing 'max' connections should fill the pool to the // top... Connection[] open = new Connection[max]; for (int i = 0; i < max; i++) { open[i] = ds.getConnection(); } // all connections are in use, so managePool should do nothing assertEquals(max, ds.poolSize()); ds.managePool(); assertEquals(max, ds.poolSize()); } @Test public void testValidateUnchecked() { final PoolAwareConnection[] connections = validConnections(4); params.setMinConnections(4); params.setMaxConnections(10); UnmanagedPoolingDataSource ds = new UnmanagedPoolingDataSource(nonPooling, params) { int i; @Override PoolAwareConnection createWrapped() throws SQLException { return connections[i++]; } }; // now that the pool is created, invalidate a few leading connections when(connections[0].validate()).thenReturn(false); when(connections[1].validate()).thenReturn(false); Connection faceHeadConnection = mock(Connection.class); PoolAwareConnection fakeHead = mock(PoolAwareConnection.class); when(fakeHead.getConnection()).thenReturn(faceHeadConnection); assertSame(connections[2], ds.validateUnchecked(fakeHead)); } @Test public void testGetConnection_UpperCap() throws SQLException { int max = 5; params.setMaxConnections(max); params.setMaxQueueWaitTime(1000); UnmanagedPoolingDataSource ds = new UnmanagedPoolingDataSource(nonPooling, params); Connection[] unchecked = new Connection[max]; for (int i = 0; i < max; i++) { unchecked[i] = ds.getConnection(); } try { ds.getConnection(); fail("Pool overflow not checked"); } catch (SQLException e) { // expected ... all connections are taken } // return one connection ... it should become immediately available unchecked[0].close(); Connection c = ds.getConnection(); assertNotNull(c); } PoolAwareConnection[] validConnections(int size) { PoolAwareConnection[] connections = new PoolAwareConnection[size]; for (int i = 0; i < size; i++) { Connection c = mock(Connection.class); connections[i] = mock(PoolAwareConnection.class); when(connections[i].getConnection()).thenReturn(c); when(connections[i].validate()).thenReturn(true); } return connections; } }