/** * 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 java.io.EOFException; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.util.Arrays; import java.util.concurrent.Callable; 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 { BufferedRandomAccessFile file = createTempFile("braf"); // writting string of data to the file byte[] data = "Hello".getBytes(); file.write(data); assertEquals(file.length(), data.length); assertEquals(file.getFilePointer(), data.length); // reading small amount of data from file, this is handled by initial buffer file.seek(0); byte[] buffer = new byte[data.length]; assertEquals(file.read(buffer), data.length); assertTrue(Arrays.equals(buffer, data)); // we read exactly what we wrote assertEquals(file.read(), -1); // nothing more to read EOF assert file.bytesRemaining() == 0 && file.isEOF(); // writing buffer bigger than page size, which will trigger reBuffer() byte[] bigData = new byte[BufferedRandomAccessFile.DEFAULT_BUFFER_SIZE + 10]; for (int i = 0; i < bigData.length; i++) bigData[i] = 'd'; long initialPosition = file.getFilePointer(); file.write(bigData); // writing data assertEquals(file.getFilePointer(), initialPosition + bigData.length); assertEquals(file.length(), initialPosition + bigData.length); // file size should equals to last position // reading written buffer file.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) file.read(); // this will trigger reBuffer() sizeRead++; } assertEquals(sizeRead, data.length); // read exactly data.length bytes assertEquals(file.getFilePointer(), initialPosition + data.length); assertEquals(file.length(), initialPosition + bigData.length); assertTrue(Arrays.equals(bigData, data)); assert file.bytesRemaining() == 0 && file.isEOF(); // we are at the of the file // test readBytes(int) method file.seek(0); ByteBuffer fileContent = file.readBytes((int) file.length()); assertEquals(fileContent.limit(), file.length()); assert ByteBufferUtil.string(fileContent).equals("Hello" + new String(bigData)); // read the same buffer but using readFully(int) data = new byte[bigData.length]; file.seek(initialPosition); file.readFully(data); assert file.bytesRemaining() == 0 && file.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(file.read(), -1); assertEquals(file.read(data), -1); assertEquals(file.read(data, 0, data.length), -1); // test read(byte[], int, int) file.seek(0); data = new byte[20]; assertEquals(file.read(data, 0, 15), 15); assertTrue(new String(data).contains("Hellodddddddddd")); for (int i = 16; i < data.length; i++) { assert data[i] == 0; } // try to seek past EOF file.seek(file.length() + 10); // should not throw an exception assert file.bytesRemaining() == 0 && file.isEOF(); file.close(); } @Test public void testReadsAndWriteOnCapacity() throws IOException { File tmpFile = File.createTempFile("readtest", "bin"); BufferedRandomAccessFile rw = new BufferedRandomAccessFile(tmpFile, "rw"); // Fully write the file and sync.. byte[] in = new byte[BufferedRandomAccessFile.DEFAULT_BUFFER_SIZE]; rw.write(in); // Read it into a same size array. byte[] out = new byte[BufferedRandomAccessFile.DEFAULT_BUFFER_SIZE]; rw.read(out); // We're really at the end. long rem = rw.bytesRemaining(); assert rw.isEOF(); assert rem == 0 : "BytesRemaining should be 0 but it's " + rem; // Cannot read any more. int negone = rw.read(); assert negone == -1 : "We read past the end of the file, should have gotten EOF -1. Instead, " + negone; // Writing will succeed rw.write(new byte[BufferedRandomAccessFile.DEFAULT_BUFFER_SIZE]); // Forcing a rebuffer here rw.write(42); } @Test public void testLength() throws IOException { File tmpFile = File.createTempFile("lengthtest", "bin"); BufferedRandomAccessFile rw = new BufferedRandomAccessFile(tmpFile, "rw"); assertEquals(0, rw.length()); // write a chunk smaller then our buffer, so will not be flushed // to disk byte[] lessThenBuffer = new byte[BufferedRandomAccessFile.DEFAULT_BUFFER_SIZE / 2]; rw.write(lessThenBuffer); assertEquals(lessThenBuffer.length, rw.length()); // sync the data and check length rw.sync(); assertEquals(lessThenBuffer.length, rw.length()); // write more then the buffer can hold and check length byte[] biggerThenBuffer = new byte[BufferedRandomAccessFile.DEFAULT_BUFFER_SIZE * 2]; rw.write(biggerThenBuffer); assertEquals(biggerThenBuffer.length + lessThenBuffer.length, rw.length()); // checking that reading doesn't interfere rw.seek(0); rw.read(); assertEquals(biggerThenBuffer.length + lessThenBuffer.length, rw.length()); rw.close(); // will use cachedlength BufferedRandomAccessFile r = new BufferedRandomAccessFile(tmpFile, "r"); assertEquals(lessThenBuffer.length + biggerThenBuffer.length, r.length()); r.close(); } @Test public void testReadBytes() throws IOException { final BufferedRandomAccessFile file = createTempFile("brafReadBytes"); byte[] data = new byte[BufferedRandomAccessFile.DEFAULT_BUFFER_SIZE + 10]; for (int i = 0; i < data.length; i++) { data[i] = 'c'; } file.write(data); file.seek(0); ByteBuffer content = file.readBytes((int) file.length()); // after reading whole file we should be at EOF assertEquals(ByteBufferUtil.compare(content, data), 0); assert file.bytesRemaining() == 0 && file.isEOF(); file.seek(0); content = file.readBytes(10); // reading first 10 bytes assertEquals(ByteBufferUtil.compare(content, "cccccccccc".getBytes()), 0); assertEquals(file.bytesRemaining(), file.length() - content.limit()); // trying to read more than file has right now expectEOF(new Callable<Object>() { public Object call() throws IOException { return file.readBytes((int) file.length() + 10); } }); file.close(); } @Test public void testSeek() throws Exception { final BufferedRandomAccessFile file = createTempFile("brafSeek"); byte[] data = new byte[BufferedRandomAccessFile.DEFAULT_BUFFER_SIZE + 20]; for (int i = 0; i < data.length; i++) { data[i] = 'c'; } file.write(data); assert file.bytesRemaining() == 0 && file.isEOF(); 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 file.seek(file.length() + 30); assertEquals(file.getFilePointer(), data.length + 30); assertEquals(file.getFilePointer(), file.length()); // length should be at seek position assert file.bytesRemaining() == 0 && file.isEOF(); expectException(new Callable<Object>() { public Object call() throws IOException { file.seek(-1); return null; } }, IllegalArgumentException.class); // throws IllegalArgumentException file.close(); } @Test public void testSkipBytes() throws IOException { BufferedRandomAccessFile file = createTempFile("brafSkipBytes"); byte[] data = new byte[BufferedRandomAccessFile.DEFAULT_BUFFER_SIZE * 2]; file.write(data); assert file.bytesRemaining() == 0 && file.isEOF(); 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(); } @Test public void testGetFilePointer() throws IOException { final BufferedRandomAccessFile file = createTempFile("brafGetFilePointer"); assertEquals(file.getFilePointer(), 0); // initial position should be 0 file.write(new byte[20]); assertEquals(file.getFilePointer(), 20); // position 20 after writing 20 bytes file.seek(10); assertEquals(file.getFilePointer(), 10); // after seek to 10 should be 10 expectException(new Callable<Object>() { public Object call() throws IOException { file.seek(-1); return null; } }, IllegalArgumentException.class); assertEquals(file.getFilePointer(), 10); file.seek(30); // past previous end file assertEquals(file.getFilePointer(), 30); // position should change after skip bytes file.seek(0); file.skipBytes(15); assertEquals(file.getFilePointer(), 15); file.read(); assertEquals(file.getFilePointer(), 16); file.read(new byte[4]); assertEquals(file.getFilePointer(), 20); file.close(); } @Test public void testGetPath() throws IOException { BufferedRandomAccessFile file = createTempFile("brafGetPath"); assert file.getPath().contains("brafGetPath"); } @Test public void testIsEOF() throws IOException { for (String mode : Arrays.asList("r", "rw")) // read, read+write { 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)) { final BufferedRandomAccessFile file = new BufferedRandomAccessFile(writeTemporaryFile(new byte[16]), mode, bufferSize); expectEOF(new Callable<Object>() { public Object call() throws IOException { file.readFully(target, offset, 17); return null; } }); } // first read is ok but eventually EOFs for (final int n : Arrays.asList(1, 2, 4, 8)) { final BufferedRandomAccessFile file = new BufferedRandomAccessFile(writeTemporaryFile(new byte[16]), mode, bufferSize); expectEOF(new Callable<Object>() { public Object call() throws IOException { while (true) file.readFully(target, 0, n); } }); } } } } @Test public void testNotEOF() throws IOException { assertEquals(1, new BufferedRandomAccessFile(writeTemporaryFile(new byte[1]), "rw").read(new byte[2])); } @Test public void testBytesRemaining() throws IOException { BufferedRandomAccessFile file = createTempFile("brafBytesRemaining"); assertEquals(file.bytesRemaining(), 0); int toWrite = BufferedRandomAccessFile.DEFAULT_BUFFER_SIZE + 10; file.write(new byte[toWrite]); assertEquals(file.bytesRemaining(), 0); file.seek(0); assertEquals(file.bytesRemaining(), toWrite); for (int i = 1; i <= file.length(); i++) { file.read(); assertEquals(file.bytesRemaining(), file.length() - i); } file.seek(0); file.skipBytes(10); assertEquals(file.bytesRemaining(), file.length() - 10); file.close(); } @Test public void testBytesPastMark() throws IOException { File tmpFile = File.createTempFile("overflowtest", "bin"); tmpFile.deleteOnExit(); // Create the BRAF by filename instead of by file. final BufferedRandomAccessFile rw = new BufferedRandomAccessFile(tmpFile.getPath(), "rw"); assert tmpFile.getPath().equals(rw.getPath()); // Create a mark and move the rw there. final FileMark mark = rw.mark(); rw.reset(mark); // Expect this call to succeed. rw.bytesPastMark(mark); } @Test public void testClose() throws IOException { final BufferedRandomAccessFile file = createTempFile("brafClose"); byte[] data = new byte[BufferedRandomAccessFile.DEFAULT_BUFFER_SIZE + 20]; for (int i = 0; i < data.length; i++) { data[i] = 'c'; } file.write(data); file.close(); expectException(new Callable<Object>() { public Object call() throws IOException { return file.read(); } }, ClosedChannelException.class); expectException(new Callable<Object>() { public Object call() throws IOException { file.write(new byte[1]); return null; } }, ClosedChannelException.class); BufferedRandomAccessFile copy = new BufferedRandomAccessFile(file.getPath(), "r"); ByteBuffer contents = copy.readBytes((int) copy.length()); assertEquals(contents.limit(), data.length); assertEquals(ByteBufferUtil.compare(contents, data), 0); } @Test public void testMarkAndReset() throws IOException { BufferedRandomAccessFile file = createTempFile("brafTestMark"); file.write(new byte[30]); file.seek(10); FileMark 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(); } @Test (expected = AssertionError.class) public void testAssertionErrorWhenBytesPastMarkIsNegative() throws IOException { BufferedRandomAccessFile file = createTempFile("brafAssertionErrorWhenBytesPastMarkIsNegative"); file.write(new byte[30]); file.seek(10); file.mark(); file.seek(0); file.bytesPastMark(); } @Test public void testReadOnly() throws IOException { BufferedRandomAccessFile 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 BufferedRandomAccessFile copy = new BufferedRandomAccessFile(file.getPath(), "r"); copy.seek(copy.length()); assertTrue(copy.bytesRemaining() == 0 && copy.isEOF()); // can't seek past the end of the file for read-only files expectEOF(new Callable<Object>() { public Object call() throws IOException { copy.seek(copy.length() + 1); return null; } }); /* Any write() call should fail */ expectException(new Callable<Object>() { public Object call() throws IOException { copy.write(1); return null; } }, IOException.class); expectException(new Callable<Object>() { public Object call() throws IOException { copy.write(new byte[1]); return null; } }, IOException.class); expectException(new Callable<Object>() { public Object call() throws IOException { copy.write(new byte[3], 0, 2); return null; } }, IOException.class); copy.seek(0); copy.skipBytes(5); assertEquals(copy.bytesRemaining(), 15); assertEquals(copy.getFilePointer(), 5); assertTrue(!copy.isEOF()); copy.seek(0); ByteBuffer contents = copy.readBytes((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.close(); copy.close(); } @Test public void testSeekPastEOF() throws IOException { BufferedRandomAccessFile file = createTempFile("brafTestSeekPastEOF"); file.seek(1); file.write(1); file.seek(0); assertEquals(0, file.read()); assertEquals(1, file.read()); } private void expectException(Callable<?> callable, Class<?> exception) { boolean thrown = false; try { callable.call(); } catch (Exception e) { assert e.getClass().equals(exception) : e.getClass().getName() + " is not " + exception.getName(); thrown = true; } assert thrown : exception.getName() + " not received"; } private void expectEOF(Callable<?> callable) { expectException(callable, EOFException.class); } private BufferedRandomAccessFile createTempFile(String name) throws IOException { File tempFile = File.createTempFile(name, null); tempFile.deleteOnExit(); return new BufferedRandomAccessFile(tempFile, "rw"); } private File writeTemporaryFile(byte[] data) throws IOException { File f = File.createTempFile("BRAFTestFile", null); f.deleteOnExit(); FileOutputStream fout = new FileOutputStream(f); fout.write(data); fout.getFD().sync(); fout.close(); return f; } }