| /** |
| * 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.cassandra.io.util; |
| import org.apache.cassandra.utils.ByteBufferUtil; |
| import org.apache.cassandra.utils.SyncUtil; |
| |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.nio.ByteBuffer; |
| import java.util.Arrays; |
| |
| import static org.apache.cassandra.Util.expectEOF; |
| import static org.apache.cassandra.Util.expectException; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.assertEquals; |
| |
| import org.junit.Test; |
| |
| public class BufferedRandomAccessFileTest |
| { |
| @Test |
| public void testReadAndWrite() throws Exception |
| { |
| SequentialWriter w = createTempFile("braf"); |
| ChannelProxy channel = new ChannelProxy(w.getPath()); |
| |
| // writting string of data to the file |
| byte[] data = "Hello".getBytes(); |
| w.write(data); |
| assertEquals(data.length, w.length()); |
| assertEquals(data.length, w.position()); |
| |
| w.sync(); |
| |
| // reading small amount of data from file, this is handled by initial buffer |
| RandomAccessReader r = RandomAccessReader.open(channel); |
| |
| byte[] buffer = new byte[data.length]; |
| assertEquals(data.length, r.read(buffer)); |
| assertTrue(Arrays.equals(buffer, data)); // we read exactly what we wrote |
| assertEquals(r.read(), -1); // nothing more to read EOF |
| assert r.bytesRemaining() == 0 && r.isEOF(); |
| |
| r.close(); |
| |
| // writing buffer bigger than page size, which will trigger reBuffer() |
| byte[] bigData = new byte[RandomAccessReader.DEFAULT_BUFFER_SIZE + 10]; |
| |
| for (int i = 0; i < bigData.length; i++) |
| bigData[i] = 'd'; |
| |
| long initialPosition = w.position(); |
| w.write(bigData); // writing data |
| assertEquals(w.position(), initialPosition + bigData.length); |
| assertEquals(w.length(), initialPosition + bigData.length); // file size should equals to last position |
| |
| w.sync(); |
| |
| r = RandomAccessReader.open(channel); // re-opening file in read-only mode |
| |
| // reading written buffer |
| r.seek(initialPosition); // back to initial (before write) position |
| data = new byte[bigData.length]; |
| long sizeRead = 0; |
| for (int i = 0; i < data.length; i++) |
| { |
| data[i] = (byte) r.read(); |
| sizeRead++; |
| } |
| |
| assertEquals(sizeRead, data.length); // read exactly data.length bytes |
| assertEquals(r.getFilePointer(), initialPosition + data.length); |
| assertEquals(r.length(), initialPosition + bigData.length); |
| assertTrue(Arrays.equals(bigData, data)); |
| assertTrue(r.bytesRemaining() == 0 && r.isEOF()); // we are at the of the file |
| |
| // test readBytes(int) method |
| r.seek(0); |
| ByteBuffer fileContent = ByteBufferUtil.read(r, (int) w.length()); |
| assertEquals(fileContent.limit(), w.length()); |
| assert ByteBufferUtil.string(fileContent).equals("Hello" + new String(bigData)); |
| |
| // read the same buffer but using readFully(int) |
| data = new byte[bigData.length]; |
| r.seek(initialPosition); |
| r.readFully(data); |
| assert r.bytesRemaining() == 0 && r.isEOF(); // we should be at EOF |
| assertTrue(Arrays.equals(bigData, data)); |
| |
| // try to read past mark (all methods should return -1) |
| data = new byte[10]; |
| assertEquals(r.read(), -1); |
| assertEquals(r.read(data), -1); |
| assertEquals(r.read(data, 0, data.length), -1); |
| |
| // test read(byte[], int, int) |
| r.seek(0); |
| data = new byte[20]; |
| assertEquals(15, r.read(data, 0, 15)); |
| assertTrue(new String(data).contains("Hellodddddddddd")); |
| for (int i = 16; i < data.length; i++) |
| { |
| assert data[i] == 0; |
| } |
| |
| w.finish(); |
| r.close(); |
| channel.close(); |
| } |
| |
| @Test |
| public void testReadAndWriteOnCapacity() throws IOException |
| { |
| File tmpFile = File.createTempFile("readtest", "bin"); |
| SequentialWriter w = SequentialWriter.open(tmpFile); |
| |
| // Fully write the file and sync.. |
| byte[] in = generateByteArray(RandomAccessReader.DEFAULT_BUFFER_SIZE); |
| w.write(in); |
| |
| ChannelProxy channel = new ChannelProxy(w.getPath()); |
| RandomAccessReader r = RandomAccessReader.open(channel); |
| |
| // Read it into a same size array. |
| byte[] out = new byte[RandomAccessReader.DEFAULT_BUFFER_SIZE]; |
| r.read(out); |
| |
| // Cannot read any more. |
| int negone = r.read(); |
| assert negone == -1 : "We read past the end of the file, should have gotten EOF -1. Instead, " + negone; |
| |
| r.close(); |
| w.finish(); |
| channel.close(); |
| } |
| |
| @Test |
| public void testLength() throws IOException |
| { |
| File tmpFile = File.createTempFile("lengthtest", "bin"); |
| SequentialWriter w = SequentialWriter.open(tmpFile); |
| assertEquals(0, w.length()); |
| |
| // write a chunk smaller then our buffer, so will not be flushed |
| // to disk |
| byte[] lessThenBuffer = generateByteArray(RandomAccessReader.DEFAULT_BUFFER_SIZE / 2); |
| w.write(lessThenBuffer); |
| assertEquals(lessThenBuffer.length, w.length()); |
| |
| // sync the data and check length |
| w.sync(); |
| assertEquals(lessThenBuffer.length, w.length()); |
| |
| // write more then the buffer can hold and check length |
| byte[] biggerThenBuffer = generateByteArray(RandomAccessReader.DEFAULT_BUFFER_SIZE * 2); |
| w.write(biggerThenBuffer); |
| assertEquals(biggerThenBuffer.length + lessThenBuffer.length, w.length()); |
| |
| w.finish(); |
| |
| // will use cachedlength |
| try (ChannelProxy channel = new ChannelProxy(tmpFile); |
| RandomAccessReader r = RandomAccessReader.open(channel)) |
| { |
| assertEquals(lessThenBuffer.length + biggerThenBuffer.length, r.length()); |
| } |
| } |
| |
| @Test |
| public void testReadBytes() throws IOException |
| { |
| final SequentialWriter w = createTempFile("brafReadBytes"); |
| |
| byte[] data = new byte[RandomAccessReader.DEFAULT_BUFFER_SIZE + 10]; |
| |
| for (int i = 0; i < data.length; i++) |
| { |
| data[i] = 'c'; |
| } |
| |
| w.write(data); |
| w.sync(); |
| |
| final ChannelProxy channel = new ChannelProxy(w.getPath()); |
| final RandomAccessReader r = RandomAccessReader.open(channel); |
| |
| ByteBuffer content = ByteBufferUtil.read(r, (int) r.length()); |
| |
| // after reading whole file we should be at EOF |
| assertEquals(0, ByteBufferUtil.compare(content, data)); |
| assert r.bytesRemaining() == 0 && r.isEOF(); |
| |
| r.seek(0); |
| content = ByteBufferUtil.read(r, 10); // reading first 10 bytes |
| assertEquals(ByteBufferUtil.compare(content, "cccccccccc".getBytes()), 0); |
| assertEquals(r.bytesRemaining(), r.length() - content.limit()); |
| |
| // trying to read more than file has right now |
| expectEOF(() -> ByteBufferUtil.read(r, (int) r.length() + 10)); |
| |
| w.finish(); |
| r.close(); |
| channel.close(); |
| } |
| |
| @Test |
| public void testSeek() throws Exception |
| { |
| SequentialWriter w = createTempFile("brafSeek"); |
| byte[] data = generateByteArray(RandomAccessReader.DEFAULT_BUFFER_SIZE + 20); |
| w.write(data); |
| w.finish(); |
| |
| final ChannelProxy channel = new ChannelProxy(w.getPath()); |
| final RandomAccessReader file = RandomAccessReader.open(channel); |
| |
| file.seek(0); |
| assertEquals(file.getFilePointer(), 0); |
| assertEquals(file.bytesRemaining(), file.length()); |
| |
| file.seek(20); |
| assertEquals(file.getFilePointer(), 20); |
| assertEquals(file.bytesRemaining(), file.length() - 20); |
| |
| // trying to seek past the end of the file should produce EOFException |
| expectException(() -> { file.seek(file.length() + 30); return null; }, IllegalArgumentException.class); |
| |
| expectException(() -> { file.seek(-1); return null; }, IllegalArgumentException.class); // throws IllegalArgumentException |
| |
| file.close(); |
| channel.close(); |
| } |
| |
| @Test |
| public void testSkipBytes() throws IOException |
| { |
| SequentialWriter w = createTempFile("brafSkipBytes"); |
| w.write(generateByteArray(RandomAccessReader.DEFAULT_BUFFER_SIZE * 2)); |
| w.finish(); |
| |
| ChannelProxy channel = new ChannelProxy(w.getPath()); |
| RandomAccessReader file = RandomAccessReader.open(channel); |
| |
| file.seek(0); // back to the beginning of the file |
| assertEquals(file.skipBytes(10), 10); |
| assertEquals(file.bytesRemaining(), file.length() - 10); |
| |
| int initialPosition = (int) file.getFilePointer(); |
| // can't skip more than file size |
| assertEquals(file.skipBytes((int) file.length() + 10), file.length() - initialPosition); |
| assertEquals(file.getFilePointer(), file.length()); |
| assert file.bytesRemaining() == 0 && file.isEOF(); |
| |
| file.seek(0); |
| |
| // skipping negative amount should return 0 |
| assertEquals(file.skipBytes(-1000), 0); |
| assertEquals(file.getFilePointer(), 0); |
| assertEquals(file.bytesRemaining(), file.length()); |
| |
| file.close(); |
| channel.close(); |
| } |
| |
| @Test |
| public void testGetFilePointer() throws IOException |
| { |
| final SequentialWriter w = createTempFile("brafGetFilePointer"); |
| |
| assertEquals(w.position(), 0); // initial position should be 0 |
| |
| w.write(generateByteArray(20)); |
| assertEquals(w.position(), 20); // position 20 after writing 20 bytes |
| |
| w.sync(); |
| |
| ChannelProxy channel = new ChannelProxy(w.getPath()); |
| RandomAccessReader r = RandomAccessReader.open(channel); |
| |
| // position should change after skip bytes |
| r.seek(0); |
| r.skipBytes(15); |
| assertEquals(r.getFilePointer(), 15); |
| |
| r.read(); |
| assertEquals(r.getFilePointer(), 16); |
| r.read(new byte[4]); |
| assertEquals(r.getFilePointer(), 20); |
| |
| w.finish(); |
| r.close(); |
| channel.close(); |
| } |
| |
| @Test |
| public void testGetPath() throws IOException |
| { |
| SequentialWriter file = createTempFile("brafGetPath"); |
| assert file.getPath().contains("brafGetPath"); |
| file.finish(); |
| } |
| |
| @Test |
| public void testIsEOF() throws IOException |
| { |
| for (int bufferSize : Arrays.asList(1, 2, 3, 5, 8, 64)) // smaller, equal, bigger buffer sizes |
| { |
| final byte[] target = new byte[32]; |
| |
| // single too-large read |
| for (final int offset : Arrays.asList(0, 8)) |
| { |
| File file1 = writeTemporaryFile(new byte[16]); |
| try (final ChannelProxy channel = new ChannelProxy(file1); |
| final RandomAccessReader file = new RandomAccessReader.Builder(channel) |
| .bufferSize(bufferSize) |
| .build()) |
| { |
| expectEOF(() -> { file.readFully(target, offset, 17); return null; }); |
| } |
| } |
| |
| // first read is ok but eventually EOFs |
| for (final int n : Arrays.asList(1, 2, 4, 8)) |
| { |
| File file1 = writeTemporaryFile(new byte[16]); |
| try (final ChannelProxy channel = new ChannelProxy(file1); |
| final RandomAccessReader file = new RandomAccessReader.Builder(channel).bufferSize(bufferSize).build()) |
| { |
| expectEOF(() -> { |
| while (true) |
| file.readFully(target, 0, n); |
| }); |
| } |
| } |
| } |
| } |
| |
| @Test |
| public void testNotEOF() throws IOException |
| { |
| try (final RandomAccessReader reader = RandomAccessReader.open(writeTemporaryFile(new byte[1]))) |
| { |
| assertEquals(1, reader.read(new byte[2])); |
| } |
| } |
| |
| @Test |
| public void testBytesRemaining() throws IOException |
| { |
| SequentialWriter w = createTempFile("brafBytesRemaining"); |
| |
| int toWrite = RandomAccessReader.DEFAULT_BUFFER_SIZE + 10; |
| |
| w.write(generateByteArray(toWrite)); |
| |
| w.sync(); |
| |
| ChannelProxy channel = new ChannelProxy(w.getPath()); |
| RandomAccessReader r = RandomAccessReader.open(channel); |
| |
| assertEquals(r.bytesRemaining(), toWrite); |
| |
| for (int i = 1; i <= r.length(); i++) |
| { |
| r.read(); |
| assertEquals(r.bytesRemaining(), r.length() - i); |
| } |
| |
| r.seek(0); |
| r.skipBytes(10); |
| assertEquals(r.bytesRemaining(), r.length() - 10); |
| |
| w.finish(); |
| r.close(); |
| channel.close(); |
| } |
| |
| @Test |
| public void testBytesPastMark() throws IOException |
| { |
| File tmpFile = File.createTempFile("overflowtest", "bin"); |
| tmpFile.deleteOnExit(); |
| |
| // Create the BRAF by filename instead of by file. |
| try (final RandomAccessReader r = RandomAccessReader.open(new File(tmpFile.getPath()))) |
| { |
| assert tmpFile.getPath().equals(r.getPath()); |
| |
| // Create a mark and move the rw there. |
| final DataPosition mark = r.mark(); |
| r.reset(mark); |
| |
| // Expect this call to succeed. |
| r.bytesPastMark(mark); |
| } |
| } |
| |
| @Test |
| public void testClose() throws IOException |
| { |
| final SequentialWriter w = createTempFile("brafClose"); |
| |
| byte[] data = generateByteArray(RandomAccessReader.DEFAULT_BUFFER_SIZE + 20); |
| |
| w.write(data); |
| w.finish(); |
| |
| final RandomAccessReader r = RandomAccessReader.open(new File(w.getPath())); |
| |
| r.close(); // closing to test read after close |
| |
| expectException(() -> r.read(), NullPointerException.class); |
| |
| //Used to throw ClosedChannelException, but now that it extends BDOSP it just NPEs on the buffer |
| //Writing to a BufferedOutputStream that is closed generates no error |
| //Going to allow the NPE to throw to catch as a bug any use after close. Notably it won't throw NPE for a |
| //write of a 0 length, but that is kind of a corner case |
| expectException(() -> { w.write(generateByteArray(1)); return null; }, NullPointerException.class); |
| |
| try (RandomAccessReader copy = RandomAccessReader.open(new File(r.getPath()))) |
| { |
| ByteBuffer contents = ByteBufferUtil.read(copy, (int) copy.length()); |
| |
| assertEquals(contents.limit(), data.length); |
| assertEquals(ByteBufferUtil.compare(contents, data), 0); |
| } |
| } |
| |
| @Test |
| public void testMarkAndReset() throws IOException |
| { |
| SequentialWriter w = createTempFile("brafTestMark"); |
| w.write(new byte[30]); |
| |
| w.finish(); |
| |
| ChannelProxy channel = new ChannelProxy(w.getPath()); |
| RandomAccessReader file = RandomAccessReader.open(channel); |
| |
| file.seek(10); |
| DataPosition mark = file.mark(); |
| |
| file.seek(file.length()); |
| assertTrue(file.isEOF()); |
| |
| file.reset(); |
| assertEquals(file.bytesRemaining(), 20); |
| |
| file.seek(file.length()); |
| assertTrue(file.isEOF()); |
| |
| file.reset(mark); |
| assertEquals(file.bytesRemaining(), 20); |
| |
| file.seek(file.length()); |
| assertEquals(file.bytesPastMark(), 20); |
| assertEquals(file.bytesPastMark(mark), 20); |
| |
| file.reset(mark); |
| assertEquals(file.bytesPastMark(), 0); |
| |
| file.close(); |
| channel.close(); |
| } |
| |
| @Test(expected = AssertionError.class) |
| public void testAssertionErrorWhenBytesPastMarkIsNegative() throws IOException |
| { |
| try (SequentialWriter w = createTempFile("brafAssertionErrorWhenBytesPastMarkIsNegative")) |
| { |
| w.write(new byte[30]); |
| w.flush(); |
| |
| try (ChannelProxy channel = new ChannelProxy(w.getPath()); |
| RandomAccessReader r = RandomAccessReader.open(channel)) |
| { |
| r.seek(10); |
| r.mark(); |
| |
| r.seek(0); |
| r.bytesPastMark(); |
| } |
| } |
| } |
| |
| @Test |
| public void testReadOnly() throws IOException |
| { |
| SequentialWriter file = createTempFile("brafReadOnlyTest"); |
| |
| byte[] data = new byte[20]; |
| for (int i = 0; i < data.length; i++) |
| data[i] = 'c'; |
| |
| file.write(data); |
| file.sync(); // flushing file to the disk |
| |
| // read-only copy of the file, with fixed file length |
| final RandomAccessReader copy = RandomAccessReader.open(new File(file.getPath())); |
| |
| copy.seek(copy.length()); |
| assertTrue(copy.bytesRemaining() == 0 && copy.isEOF()); |
| |
| // can't seek past the end of the file for read-only files |
| expectException(() -> { copy.seek(copy.length() + 1); return null; }, IllegalArgumentException.class); |
| |
| copy.seek(0); |
| copy.skipBytes(5); |
| |
| assertEquals(copy.bytesRemaining(), 15); |
| assertEquals(copy.getFilePointer(), 5); |
| assertTrue(!copy.isEOF()); |
| |
| copy.seek(0); |
| ByteBuffer contents = ByteBufferUtil.read(copy, (int) copy.length()); |
| |
| assertEquals(contents.limit(), copy.length()); |
| assertTrue(ByteBufferUtil.compare(contents, data) == 0); |
| |
| copy.seek(0); |
| |
| int count = 0; |
| while (!copy.isEOF()) |
| { |
| assertEquals((byte) copy.read(), 'c'); |
| count++; |
| } |
| |
| assertEquals(count, copy.length()); |
| |
| copy.seek(0); |
| byte[] content = new byte[10]; |
| copy.read(content); |
| |
| assertEquals(new String(content), "cccccccccc"); |
| |
| file.finish(); |
| copy.close(); |
| } |
| |
| @Test (expected=IllegalArgumentException.class) |
| public void testSetNegativeLength() throws IOException, IllegalArgumentException |
| { |
| File tmpFile = File.createTempFile("set_negative_length", "bin"); |
| try (SequentialWriter file = SequentialWriter.open(tmpFile)) |
| { |
| file.truncate(-8L); |
| } |
| } |
| |
| private SequentialWriter createTempFile(String name) throws IOException |
| { |
| File tempFile = File.createTempFile(name, null); |
| tempFile.deleteOnExit(); |
| |
| return SequentialWriter.open(tempFile); |
| } |
| |
| private File writeTemporaryFile(byte[] data) throws IOException |
| { |
| File f = File.createTempFile("BRAFTestFile", null); |
| f.deleteOnExit(); |
| FileOutputStream fout = new FileOutputStream(f); |
| fout.write(data); |
| SyncUtil.sync(fout); |
| fout.close(); |
| return f; |
| } |
| |
| private byte[] generateByteArray(int length) |
| { |
| byte[] arr = new byte[length]; |
| |
| for (int i = 0; i < length; i++) |
| arr[i] = 'a'; |
| |
| return arr; |
| } |
| } |