/*
* 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;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Matchers;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.ReadableByteChannel;
import java.util.Arrays;
import org.neo4j.driver.v1.exceptions.ClientException;
import org.neo4j.driver.v1.util.RecordingByteChannel;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class ChunkedInputTest
{
@Rule
public ExpectedException exception = ExpectedException.none();
@Test
public void shouldExposeMultipleChunksAsCohesiveStream() throws Throwable
{
// Given
ReadableByteChannel channel = Channels.newChannel(
new ByteArrayInputStream( new byte[]{ 0, 5, 1, 2, 3, 4, 5} ) );
ChunkedInput ch = new ChunkedInput( 2, channel );
// When
byte[] bytes = new byte[5];
ch.readBytes( bytes, 0, 5 );
// Then
assertThat( bytes, equalTo( new byte[]{1, 2, 3, 4, 5} ) );
}
@Test
public void shouldReadIntoMisalignedDestinationBuffer() throws Throwable
{
// Given
ReadableByteChannel channel = Channels.newChannel(
new ByteArrayInputStream( new byte[]{0, 7, 1, 2, 3, 4, 5, 6, 7} ) );
ChunkedInput ch = new ChunkedInput( 2, channel );
byte[] bytes = new byte[3];
// When I read {1,2,3}
ch.readBytes( bytes, 0, 3 );
// Then
assertThat( bytes, equalTo( new byte[]{1, 2, 3} ) );
// When I read {4,5,6}
ch.readBytes( bytes, 0, 3 );
// Then
assertThat( bytes, equalTo( new byte[]{4, 5, 6} ) );
// When I read {7}
Arrays.fill( bytes, (byte) 0 );
ch.readBytes( bytes, 0, 1 );
// Then
assertThat( bytes, equalTo( new byte[]{7, 0, 0} ) );
}
@Test
public void canReadBytesAcrossChunkBoundaries() throws Exception
{
// Given
byte[] inputBuffer = {
0, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // chunk 1 with size 10
0, 5, 1, 2, 3, 4, 5 // chunk 2 with size 5
};
RecordingByteChannel ch = new RecordingByteChannel();
ch.write( ByteBuffer.wrap( inputBuffer ) );
ChunkedInput input = new ChunkedInput( ch );
byte[] outputBuffer = new byte[15];
// When
input.hasMoreData();
// Then
input.readBytes( outputBuffer, 0, 15 );
assertThat( outputBuffer, equalTo( new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5} ) );
}
@Test
public void canReadAllIntegerSizes() throws Exception
{
// Given
RecordingByteChannel ch = new RecordingByteChannel();
ChunkedOutput out = new ChunkedOutput( ch );
// these are written in one go on purpose, to check for buffer pointer errors where writes
// would interfere with one another, writing at the wrong offsets
out.writeByte( Byte.MAX_VALUE );
out.writeByte( (byte)1 );
out.writeByte( Byte.MIN_VALUE );
out.writeLong( Long.MAX_VALUE );
out.writeLong( 0l );
out.writeLong( Long.MIN_VALUE );
out.writeShort( Short.MAX_VALUE );
out.writeShort( (short)0 );
out.writeShort( Short.MIN_VALUE );
out.writeInt( Integer.MAX_VALUE );
out.writeInt( 0 );
out.writeInt( Integer.MIN_VALUE );
out.flush();
ChunkedInput in = new ChunkedInput( ch );
// when / then
assertEquals( Byte.MAX_VALUE, in.readByte() );
assertEquals( (byte)1, in.readByte() );
assertEquals( Byte.MIN_VALUE, in.readByte() );
assertEquals( Long.MAX_VALUE, in.readLong() );
assertEquals( 0l, in.readLong() );
assertEquals( Long.MIN_VALUE, in.readLong() );
assertEquals( Short.MAX_VALUE, in.readShort() );
assertEquals( (short)0, in.readShort() );
assertEquals( Short.MIN_VALUE, in.readShort() );
assertEquals( Integer.MAX_VALUE, in.readInt() );
assertEquals( 0, in.readInt() );
assertEquals( Integer.MIN_VALUE, in.readInt() );
}
@Test
public void shouldNotReadMessageEndingWhenByteLeftInBuffer()
{
// Given
ReadableByteChannel channel = Channels.newChannel(
new ByteArrayInputStream( new byte[]{ 0, 5, 1, 2, 3, 4, 5, 0, 0} ) );
ChunkedInput ch = new ChunkedInput( 2, channel );
byte[] bytes = new byte[4];
ch.readBytes( bytes, 0, 4 );
assertThat( bytes, equalTo( new byte[]{1, 2, 3, 4} ) );
// When
try
{
ch.messageBoundaryHook().run();
fail( "The expected ClientException is not thrown" );
}
catch ( ClientException e )
{
assertEquals( "org.neo4j.driver.v1.exceptions.ClientException: Trying to read message complete ending " +
"'00 00' while there are more data left in the message content unread: buffer [], " +
"unread chunk size 1", e.toString() );
}
}
@Test
public void shouldGiveHelpfulMessageOnInterrupt() throws IOException
{
// Given
ReadableByteChannel channel = mock(ReadableByteChannel.class);
when(channel.read( Matchers.any(ByteBuffer.class) )).thenThrow( new ClosedByInterruptException() );
ChunkedInput ch = new ChunkedInput( 2, channel );
// Expect
exception.expectMessage( "Connection to the database was lost because someone called `interrupt()` on the driver thread waiting for a reply. " +
"This normally happens because the JVM is shutting down, but it can also happen because your application code or some " +
"framework you are using is manually interrupting the thread." );
// When
ch.readByte();
}
}