blob: 8cd90fbb5887753a3bbbaa17868c6a4cd02337ea [file] [log] [blame]
/*
* 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.codecs;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.store.BufferedChecksumIndexInput;
import org.apache.lucene.store.ChecksumIndexInput;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.IOContext;
import org.apache.lucene.store.IndexInput;
import org.apache.lucene.store.IndexOutput;
import org.apache.lucene.store.RAMFile;
import org.apache.lucene.store.RAMInputStream;
import org.apache.lucene.store.RAMOutputStream;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.util.StringHelper;
/** tests for codecutil methods */
public class TestCodecUtil extends LuceneTestCase {
public void testHeaderLength() throws Exception {
RAMFile file = new RAMFile();
IndexOutput output = new RAMOutputStream(file, true);
CodecUtil.writeHeader(output, "FooBar", 5);
output.writeString("this is the data");
output.close();
IndexInput input = new RAMInputStream("file", file);
input.seek(CodecUtil.headerLength("FooBar"));
assertEquals("this is the data", input.readString());
input.close();
}
public void testWriteTooLongHeader() throws Exception {
StringBuilder tooLong = new StringBuilder();
for (int i = 0; i < 128; i++) {
tooLong.append('a');
}
RAMFile file = new RAMFile();
IndexOutput output = new RAMOutputStream(file, true);
expectThrows(IllegalArgumentException.class, () -> {
CodecUtil.writeHeader(output, tooLong.toString(), 5);
});
}
public void testWriteNonAsciiHeader() throws Exception {
RAMFile file = new RAMFile();
IndexOutput output = new RAMOutputStream(file, true);
expectThrows(IllegalArgumentException.class, () -> {
CodecUtil.writeHeader(output, "\u1234", 5);
});
}
public void testReadHeaderWrongMagic() throws Exception {
RAMFile file = new RAMFile();
IndexOutput output = new RAMOutputStream(file, true);
output.writeInt(1234);
output.close();
IndexInput input = new RAMInputStream("file", file);
expectThrows(CorruptIndexException.class, () -> {
CodecUtil.checkHeader(input, "bogus", 1, 1);
});
}
public void testChecksumEntireFile() throws Exception {
RAMFile file = new RAMFile();
IndexOutput output = new RAMOutputStream(file, true);
CodecUtil.writeHeader(output, "FooBar", 5);
output.writeString("this is the data");
CodecUtil.writeFooter(output);
output.close();
IndexInput input = new RAMInputStream("file", file);
CodecUtil.checksumEntireFile(input);
input.close();
}
public void testCheckFooterValid() throws Exception {
RAMFile file = new RAMFile();
IndexOutput output = new RAMOutputStream(file, true);
CodecUtil.writeHeader(output, "FooBar", 5);
output.writeString("this is the data");
CodecUtil.writeFooter(output);
output.close();
ChecksumIndexInput input = new BufferedChecksumIndexInput(new RAMInputStream("file", file));
Exception mine = new RuntimeException("fake exception");
RuntimeException expected = expectThrows(RuntimeException.class, () -> {
CodecUtil.checkFooter(input, mine);
});
assertEquals("fake exception", expected.getMessage());
Throwable suppressed[] = expected.getSuppressed();
assertEquals(1, suppressed.length);
assertTrue(suppressed[0].getMessage().contains("checksum passed"));
input.close();
}
public void testCheckFooterValidAtFooter() throws Exception {
RAMFile file = new RAMFile();
IndexOutput output = new RAMOutputStream(file, true);
CodecUtil.writeHeader(output, "FooBar", 5);
output.writeString("this is the data");
CodecUtil.writeFooter(output);
output.close();
ChecksumIndexInput input = new BufferedChecksumIndexInput(new RAMInputStream("file", file));
CodecUtil.checkHeader(input, "FooBar", 5, 5);
assertEquals("this is the data", input.readString());
Exception mine = new RuntimeException("fake exception");
RuntimeException expected = expectThrows(RuntimeException.class, () -> {
CodecUtil.checkFooter(input, mine);
});
assertEquals("fake exception", expected.getMessage());
Throwable suppressed[] = expected.getSuppressed();
assertEquals(1, suppressed.length);
assertTrue(suppressed[0].getMessage().contains("checksum passed"));
input.close();
}
public void testCheckFooterValidPastFooter() throws Exception {
RAMFile file = new RAMFile();
IndexOutput output = new RAMOutputStream(file, true);
CodecUtil.writeHeader(output, "FooBar", 5);
output.writeString("this is the data");
CodecUtil.writeFooter(output);
output.close();
ChecksumIndexInput input = new BufferedChecksumIndexInput(new RAMInputStream("file", file));
CodecUtil.checkHeader(input, "FooBar", 5, 5);
assertEquals("this is the data", input.readString());
// bogusly read a byte too far (can happen)
input.readByte();
Exception mine = new RuntimeException("fake exception");
CorruptIndexException expected = expectThrows(CorruptIndexException.class, () -> {
CodecUtil.checkFooter(input, mine);
});
assertTrue(expected.getMessage().contains("checksum status indeterminate"));
Throwable suppressed[] = expected.getSuppressed();
assertEquals(1, suppressed.length);
assertEquals("fake exception", suppressed[0].getMessage());
input.close();
}
public void testCheckFooterInvalid() throws Exception {
RAMFile file = new RAMFile();
IndexOutput output = new RAMOutputStream(file, true);
CodecUtil.writeHeader(output, "FooBar", 5);
output.writeString("this is the data");
output.writeInt(CodecUtil.FOOTER_MAGIC);
output.writeInt(0);
output.writeLong(1234567); // write a bogus checksum
output.close();
ChecksumIndexInput input = new BufferedChecksumIndexInput(new RAMInputStream("file", file));
CodecUtil.checkHeader(input, "FooBar", 5, 5);
assertEquals("this is the data", input.readString());
Exception mine = new RuntimeException("fake exception");
CorruptIndexException expected = expectThrows(CorruptIndexException.class, () -> {
CodecUtil.checkFooter(input, mine);
});
assertTrue(expected.getMessage().contains("checksum failed"));
Throwable suppressed[] = expected.getSuppressed();
assertEquals(1, suppressed.length);
assertEquals("fake exception", suppressed[0].getMessage());
input.close();
}
public void testSegmentHeaderLength() throws Exception {
RAMFile file = new RAMFile();
IndexOutput output = new RAMOutputStream(file, true);
CodecUtil.writeIndexHeader(output, "FooBar", 5, StringHelper.randomId(), "xyz");
output.writeString("this is the data");
output.close();
IndexInput input = new RAMInputStream("file", file);
input.seek(CodecUtil.indexHeaderLength("FooBar", "xyz"));
assertEquals("this is the data", input.readString());
input.close();
}
public void testWriteTooLongSuffix() throws Exception {
StringBuilder tooLong = new StringBuilder();
for (int i = 0; i < 256; i++) {
tooLong.append('a');
}
RAMFile file = new RAMFile();
IndexOutput output = new RAMOutputStream(file, true);
expectThrows(IllegalArgumentException.class, () -> {
CodecUtil.writeIndexHeader(output, "foobar", 5, StringHelper.randomId(), tooLong.toString());
});
}
public void testWriteVeryLongSuffix() throws Exception {
StringBuilder justLongEnough = new StringBuilder();
for (int i = 0; i < 255; i++) {
justLongEnough.append('a');
}
RAMFile file = new RAMFile();
IndexOutput output = new RAMOutputStream(file, true);
byte[] id = StringHelper.randomId();
CodecUtil.writeIndexHeader(output, "foobar", 5, id, justLongEnough.toString());
output.close();
IndexInput input = new RAMInputStream("file", file);
CodecUtil.checkIndexHeader(input, "foobar", 5, 5, id, justLongEnough.toString());
assertEquals(input.getFilePointer(), input.length());
assertEquals(input.getFilePointer(), CodecUtil.indexHeaderLength("foobar", justLongEnough.toString()));
input.close();
}
public void testWriteNonAsciiSuffix() throws Exception {
RAMFile file = new RAMFile();
IndexOutput output = new RAMOutputStream(file, true);
expectThrows(IllegalArgumentException.class, () -> {
CodecUtil.writeIndexHeader(output, "foobar", 5, StringHelper.randomId(), "\u1234");
});
}
public void testReadBogusCRC() throws Exception {
RAMFile file = new RAMFile();
IndexOutput output = new RAMOutputStream(file, false);
output.writeLong(-1L); // bad
output.writeLong(1L << 32); // bad
output.writeLong(-(1L << 32)); // bad
output.writeLong((1L << 32) - 1); // ok
output.close();
IndexInput input = new RAMInputStream("file", file);
// read 3 bogus values
for (int i = 0; i < 3; i++) {
expectThrows(CorruptIndexException.class, () -> {
CodecUtil.readCRC(input);
});
}
// good value
CodecUtil.readCRC(input);
}
public void testWriteBogusCRC() throws Exception {
RAMFile file = new RAMFile();
final IndexOutput output = new RAMOutputStream(file, false);
AtomicLong fakeChecksum = new AtomicLong();
// wrap the index input where we control the checksum for mocking
IndexOutput fakeOutput = new IndexOutput("fake", "fake") {
@Override
public void close() throws IOException {
output.close();
}
@Override
public long getFilePointer() {
return output.getFilePointer();
}
@Override
public long getChecksum() throws IOException {
return fakeChecksum.get();
}
@Override
public void writeByte(byte b) throws IOException {
output.writeByte(b);
}
@Override
public void writeBytes(byte[] b, int offset, int length) throws IOException {
output.writeBytes(b, offset, length);
}
};
fakeChecksum.set(-1L); // bad
expectThrows(IllegalStateException.class, () -> {
CodecUtil.writeCRC(fakeOutput);
});
fakeChecksum.set(1L << 32); // bad
expectThrows(IllegalStateException.class, () -> {
CodecUtil.writeCRC(fakeOutput);
});
fakeChecksum.set(-(1L << 32)); // bad
expectThrows(IllegalStateException.class, () -> {
CodecUtil.writeCRC(fakeOutput);
});
fakeChecksum.set((1L << 32) - 1); // ok
CodecUtil.writeCRC(fakeOutput);
}
public void testTruncatedFileThrowsCorruptIndexException() throws IOException {
RAMFile file = new RAMFile();
IndexOutput output = new RAMOutputStream(file, false);
output.close();
IndexInput input = new RAMInputStream("file", file);
CorruptIndexException e = expectThrows(CorruptIndexException.class,
() -> CodecUtil.checksumEntireFile(input));
assertEquals("misplaced codec footer (file truncated?): length=0 but footerLength==16 (resource=RAMInputStream(name=file))", e.getMessage());
e = expectThrows(CorruptIndexException.class,
() -> CodecUtil.retrieveChecksum(input));
assertEquals("misplaced codec footer (file truncated?): length=0 but footerLength==16 (resource=RAMInputStream(name=file))", e.getMessage());
}
public void testRetrieveChecksum() throws IOException {
Directory dir = newDirectory();
try (IndexOutput out = dir.createOutput("foo", IOContext.DEFAULT)) {
out.writeByte((byte) 42);
CodecUtil.writeFooter(out);
}
try (IndexInput in = dir.openInput("foo", IOContext.DEFAULT)) {
CodecUtil.retrieveChecksum(in, in.length()); // no exception
CorruptIndexException exception = expectThrows(CorruptIndexException.class,
() -> CodecUtil.retrieveChecksum(in, in.length() - 1));
assertTrue(exception.getMessage().contains("too long"));
assertArrayEquals(new Throwable[0], exception.getSuppressed());
exception = expectThrows(CorruptIndexException.class,
() -> CodecUtil.retrieveChecksum(in, in.length() + 1));
assertTrue(exception.getMessage().contains("truncated"));
assertArrayEquals(new Throwable[0], exception.getSuppressed());
}
try (IndexOutput out = dir.createOutput("bar", IOContext.DEFAULT)) {
for (int i = 0; i <= CodecUtil.footerLength(); ++i) {
out.writeByte((byte) i);
}
}
try (IndexInput in = dir.openInput("bar", IOContext.DEFAULT)) {
CorruptIndexException exception = expectThrows(CorruptIndexException.class,
() -> CodecUtil.retrieveChecksum(in, in.length()));
assertTrue(exception.getMessage().contains("codec footer mismatch"));
assertArrayEquals(new Throwable[0], exception.getSuppressed());
exception = expectThrows(CorruptIndexException.class,
() -> CodecUtil.retrieveChecksum(in, in.length() - 1));
assertTrue(exception.getMessage().contains("too long"));
exception = expectThrows(CorruptIndexException.class,
() -> CodecUtil.retrieveChecksum(in, in.length() + 1));
assertTrue(exception.getMessage().contains("truncated"));
}
dir.close();
}
}