/* * 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.lucene.store; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Random; import org.apache.lucene.analysis.MockAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.index.IndexWriterConfig.OpenMode; import org.apache.lucene.index.Term; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.TermQuery; import org.apache.lucene.util.ArrayUtil; import org.apache.lucene.util.LuceneTestCase; public class TestBufferedIndexInput extends LuceneTestCase { private static final long TEST_FILE_LENGTH = 100*1024; // Call readByte() repeatedly, past the buffer boundary, and see that it // is working as expected. // Our input comes from a dynamically generated/ "file" - see // MyBufferedIndexInput below. public void testReadByte() throws Exception { MyBufferedIndexInput input = new MyBufferedIndexInput(); for (int i = 0; i < BufferedIndexInput.BUFFER_SIZE * 10; i++) { assertEquals(input.readByte(), byten(i)); } } // Call readBytes() repeatedly, with various chunk sizes (from 1 byte to // larger than the buffer size), and see that it returns the bytes we expect. // Our input comes from a dynamically generated "file" - // see MyBufferedIndexInput below. public void testReadBytes() throws Exception { MyBufferedIndexInput input = new MyBufferedIndexInput(); runReadBytes(input, BufferedIndexInput.BUFFER_SIZE, random()); } private void runReadBytesAndClose(IndexInput input, int bufferSize, Random r) throws IOException { try { runReadBytes(input, bufferSize, r); } finally { input.close(); } } private void runReadBytes(IndexInput input, int bufferSize, Random r) throws IOException { int pos = 0; // gradually increasing size: for (int size = 1; size < bufferSize * 10; size = size + size / 200 + 1) { checkReadBytes(input, size, pos); pos += size; if (pos >= TEST_FILE_LENGTH) { // wrap pos = 0; input.seek(0L); } } // wildly fluctuating size: for (long i = 0; i < 100; i++) { final int size = r.nextInt(10000); checkReadBytes(input, 1+size, pos); pos += 1+size; if (pos >= TEST_FILE_LENGTH) { // wrap pos = 0; input.seek(0L); } } // constant small size (7 bytes): for (int i = 0; i < bufferSize; i++) { checkReadBytes(input, 7, pos); pos += 7; if (pos >= TEST_FILE_LENGTH) { // wrap pos = 0; input.seek(0L); } } } private byte[] buffer = new byte[10]; private void checkReadBytes(IndexInput input, int size, int pos) throws IOException{ // Just to see that "offset" is treated properly in readBytes(), we // add an arbitrary offset at the beginning of the array int offset = size % 10; // arbitrary buffer = ArrayUtil.grow(buffer, offset+size); assertEquals(pos, input.getFilePointer()); long left = TEST_FILE_LENGTH - input.getFilePointer(); if (left <= 0) { return; } else if (left < size) { size = (int) left; } input.readBytes(buffer, offset, size); assertEquals(pos+size, input.getFilePointer()); for(int i=0; i<size; i++) { assertEquals("pos=" + i + " filepos=" + (pos+i), byten(pos+i), buffer[offset+i]); } } // This tests that attempts to readBytes() past an EOF will fail, while // reads up to the EOF will succeed. The EOF is determined by the // BufferedIndexInput's arbitrary length() value. public void testEOF() throws Exception { MyBufferedIndexInput input = new MyBufferedIndexInput(1024); // see that we can read all the bytes at one go: checkReadBytes(input, (int)input.length(), 0); // go back and see that we can't read more than that, for small and // large overflows: int pos = (int)input.length()-10; input.seek(pos); checkReadBytes(input, 10, pos); input.seek(pos); // block read past end of file expectThrows(IOException.class, () -> { checkReadBytes(input, 11, pos); }); input.seek(pos); // block read past end of file expectThrows(IOException.class, () -> { checkReadBytes(input, 50, pos); }); input.seek(pos); // block read past end of file expectThrows(IOException.class, () -> { checkReadBytes(input, 100000, pos); }); } // byten emulates a file - byten(n) returns the n'th byte in that file. // MyBufferedIndexInput reads this "file". private static byte byten(long n){ return (byte)(n*n%256); } private static class MyBufferedIndexInput extends BufferedIndexInput { private long pos; private long len; public MyBufferedIndexInput(long len){ super("MyBufferedIndexInput(len=" + len + ")", BufferedIndexInput.BUFFER_SIZE); this.len = len; this.pos = 0; } public MyBufferedIndexInput(){ // an infinite file this(Long.MAX_VALUE); } @Override protected void readInternal(byte[] b, int offset, int length) throws IOException { for(int i=offset; i<offset+length; i++) b[i] = byten(pos++); } @Override protected void seekInternal(long pos) throws IOException { this.pos = pos; } @Override public void close() throws IOException { } @Override public long length() { return len; } @Override public IndexInput slice(String sliceDescription, long offset, long length) throws IOException { throw new UnsupportedOperationException(); } } public void testSetBufferSize() throws IOException { Path indexDir = createTempDir("testSetBufferSize"); MockFSDirectory dir = new MockFSDirectory(indexDir, random()); IndexWriter writer = new IndexWriter( dir, new IndexWriterConfig(new MockAnalyzer(random())). setOpenMode(OpenMode.CREATE). setMergePolicy(newLogMergePolicy(false)) ); for(int i=0;i<37;i++) { Document doc = new Document(); doc.add(newTextField("content", "aaa bbb ccc ddd" + i, Field.Store.YES)); doc.add(newTextField("id", "" + i, Field.Store.YES)); writer.addDocument(doc); } dir.allIndexInputs.clear(); IndexReader reader = DirectoryReader.open(writer); Term aaa = new Term("content", "aaa"); Term bbb = new Term("content", "bbb"); reader.close(); dir.tweakBufferSizes(); writer.deleteDocuments(new Term("id", "0")); reader = DirectoryReader.open(writer); IndexSearcher searcher = newSearcher(reader); ScoreDoc[] hits = searcher.search(new TermQuery(bbb), 1000).scoreDocs; dir.tweakBufferSizes(); assertEquals(36, hits.length); reader.close(); dir.tweakBufferSizes(); writer.deleteDocuments(new Term("id", "4")); reader = DirectoryReader.open(writer); searcher = newSearcher(reader); hits = searcher.search(new TermQuery(bbb), 1000).scoreDocs; dir.tweakBufferSizes(); assertEquals(35, hits.length); dir.tweakBufferSizes(); hits = searcher.search(new TermQuery(new Term("id", "33")), 1000).scoreDocs; dir.tweakBufferSizes(); assertEquals(1, hits.length); hits = searcher.search(new TermQuery(aaa), 1000).scoreDocs; dir.tweakBufferSizes(); assertEquals(35, hits.length); writer.close(); reader.close(); } private static class MockFSDirectory extends FilterDirectory { final List<IndexInput> allIndexInputs = new ArrayList<>(); final Random rand; public MockFSDirectory(Path path, Random rand) throws IOException { super(new SimpleFSDirectory(path)); this.rand = rand; } public void tweakBufferSizes() { //int count = 0; for (final IndexInput ip : allIndexInputs) { BufferedIndexInput bii = (BufferedIndexInput) ip; int bufferSize = 1024+Math.abs(rand.nextInt() % 32768); bii.setBufferSize(bufferSize); //count++; } //System.out.println("tweak'd " + count + " buffer sizes"); } @Override public IndexInput openInput(String name, IOContext context) throws IOException { // Make random changes to buffer size //bufferSize = 1+Math.abs(rand.nextInt() % 10); IndexInput f = super.openInput(name, context); allIndexInputs.add(f); return f; } } }