/* * Copyright (c) 2002-2017 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * 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 org.neo4j.driver.internal.net.pooling; import org.junit.Test; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.PooledConnection; import org.neo4j.driver.internal.util.Consumer; import org.neo4j.driver.internal.util.Supplier; import org.neo4j.driver.v1.Logger; import org.neo4j.driver.v1.Logging; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.RETURNS_MOCKS; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.neo4j.driver.internal.net.BoltServerAddress.LOCAL_DEFAULT; import static org.neo4j.driver.internal.util.Clock.SYSTEM; public class BlockingPooledConnectionQueueTest { @SuppressWarnings( "unchecked" ) @Test public void shouldCreateNewConnectionWhenEmpty() { // Given PooledConnection connection = mock( PooledConnection.class ); Supplier<PooledConnection> supplier = mock( Supplier.class ); when( supplier.get() ).thenReturn( connection ); BlockingPooledConnectionQueue queue = newConnectionQueue( 10 ); // When queue.acquire( supplier ); // Then verify( supplier ).get(); } @SuppressWarnings( "unchecked" ) @Test public void shouldNotCreateNewConnectionWhenNotEmpty() { // Given PooledConnection connection = mock( PooledConnection.class ); Supplier<PooledConnection> supplier = mock( Supplier.class ); when( supplier.get() ).thenReturn( connection ); BlockingPooledConnectionQueue queue = newConnectionQueue( 1 ); queue.offer( connection ); // When queue.acquire( supplier ); // Then verify( supplier, never() ).get(); } @SuppressWarnings( "unchecked" ) @Test public void shouldTerminateAllSeenConnections() { // Given PooledConnection connection1 = mock( PooledConnection.class ); PooledConnection connection2 = mock( PooledConnection.class ); Supplier<PooledConnection> supplier = mock( Supplier.class ); when( supplier.get() ).thenReturn( connection1 ); BlockingPooledConnectionQueue queue = newConnectionQueue( 2 ); queue.offer( connection1 ); queue.offer( connection2 ); assertThat( queue.size(), equalTo( 2 ) ); // When queue.acquire( supplier ); assertThat( queue.size(), equalTo( 1 ) ); queue.terminate(); // Then verify( connection1 ).dispose(); verify( connection2 ).dispose(); } @Test public void shouldNotAcceptWhenFull() { // Given PooledConnection connection1 = mock( PooledConnection.class ); PooledConnection connection2 = mock( PooledConnection.class ); BlockingPooledConnectionQueue queue = newConnectionQueue( 1 ); // Then assertTrue( queue.offer( connection1 ) ); assertFalse( queue.offer( connection2 ) ); } @Test public void shouldDisposeAllConnectionsWhenOneOfThemFailsToDispose() { BlockingPooledConnectionQueue queue = newConnectionQueue( 5 ); PooledConnection connection1 = mock( PooledConnection.class ); PooledConnection connection2 = mock( PooledConnection.class ); PooledConnection connection3 = mock( PooledConnection.class ); RuntimeException disposeError = new RuntimeException( "Failed to stop socket" ); doThrow( disposeError ).when( connection2 ).dispose(); queue.offer( connection1 ); queue.offer( connection2 ); queue.offer( connection3 ); queue.terminate(); verify( connection1 ).dispose(); verify( connection2 ).dispose(); verify( connection3 ).dispose(); } @Test @SuppressWarnings( "unchecked" ) public void shouldTryToCloseAllUnderlyingConnections() { BlockingPooledConnectionQueue queue = newConnectionQueue( 5 ); Connection connection1 = mock( Connection.class ); Connection connection2 = mock( Connection.class ); Connection connection3 = mock( Connection.class ); RuntimeException closeError1 = new RuntimeException( "Failed to close 1" ); RuntimeException closeError2 = new RuntimeException( "Failed to close 2" ); RuntimeException closeError3 = new RuntimeException( "Failed to close 3" ); doThrow( closeError1 ).when( connection1 ).close(); doThrow( closeError2 ).when( connection2 ).close(); doThrow( closeError3 ).when( connection3 ).close(); PooledConnection pooledConnection1 = new PooledSocketConnection( connection1, mock( Consumer.class ), SYSTEM ); PooledConnection pooledConnection2 = new PooledSocketConnection( connection2, mock( Consumer.class ), SYSTEM ); PooledConnection pooledConnection3 = new PooledSocketConnection( connection3, mock( Consumer.class ), SYSTEM ); queue.offer( pooledConnection1 ); queue.offer( pooledConnection2 ); queue.offer( pooledConnection3 ); queue.terminate(); verify( connection1 ).close(); verify( connection2 ).close(); verify( connection3 ).close(); } @Test @SuppressWarnings( "unchecked" ) public void shouldLogWhenConnectionDisposeFails() { Logging logging = mock( Logging.class ); Logger logger = mock( Logger.class ); when( logging.getLog( anyString() ) ).thenReturn( logger ); BlockingPooledConnectionQueue queue = newConnectionQueue( 5, logging ); Connection connection = mock( Connection.class ); RuntimeException closeError = new RuntimeException( "Fail" ); doThrow( closeError ).when( connection ).close(); PooledConnection pooledConnection = new PooledSocketConnection( connection, mock( Consumer.class ), SYSTEM ); queue.offer( pooledConnection ); queue.terminate(); verify( logger ).error( anyString(), eq( closeError ) ); } @Test public void shouldHaveZeroSizeAfterTermination() { BlockingPooledConnectionQueue queue = newConnectionQueue( 5 ); queue.offer( mock( PooledConnection.class ) ); queue.offer( mock( PooledConnection.class ) ); queue.offer( mock( PooledConnection.class ) ); queue.terminate(); assertEquals( 0, queue.size() ); } @Test @SuppressWarnings( "unchecked" ) public void shouldTerminateBothAcquiredAndIdleConnections() { BlockingPooledConnectionQueue queue = newConnectionQueue( 5 ); PooledConnection connection1 = mock( PooledConnection.class ); PooledConnection connection2 = mock( PooledConnection.class ); PooledConnection connection3 = mock( PooledConnection.class ); PooledConnection connection4 = mock( PooledConnection.class ); queue.offer( connection1 ); queue.offer( connection2 ); queue.offer( connection3 ); queue.offer( connection4 ); PooledConnection acquiredConnection1 = queue.acquire( mock( Supplier.class ) ); PooledConnection acquiredConnection2 = queue.acquire( mock( Supplier.class ) ); assertSame( connection1, acquiredConnection1 ); assertSame( connection2, acquiredConnection2 ); queue.terminate(); verify( connection1 ).dispose(); verify( connection2 ).dispose(); verify( connection3 ).dispose(); verify( connection4 ).dispose(); } private static BlockingPooledConnectionQueue newConnectionQueue( int capacity ) { return newConnectionQueue( capacity, mock( Logging.class, RETURNS_MOCKS ) ); } private static BlockingPooledConnectionQueue newConnectionQueue( int capacity, Logging logging ) { return new BlockingPooledConnectionQueue( LOCAL_DEFAULT, capacity, logging ); } }