blob: 199f69fec1f3e0a306b4ff41c6a3d65c4ca94978 [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.tests.index;
import com.carrotsearch.randomizedtesting.generators.RandomPicks;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import org.apache.lucene.codecs.Codec;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DocValuesType;
import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.index.FieldInfos;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.IndexableFieldType;
import org.apache.lucene.index.PointValues;
import org.apache.lucene.index.SegmentInfo;
import org.apache.lucene.index.VectorEncoding;
import org.apache.lucene.index.VectorSimilarityFunction;
import org.apache.lucene.index.VectorValues;
import org.apache.lucene.internal.tests.IndexPackageAccess;
import org.apache.lucene.internal.tests.TestSecrets;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.IOContext;
import org.apache.lucene.tests.store.MockDirectoryWrapper;
import org.apache.lucene.tests.store.MockDirectoryWrapper.Failure;
import org.apache.lucene.tests.store.MockDirectoryWrapper.FakeIOException;
import org.apache.lucene.tests.util.TestUtil;
import org.apache.lucene.util.StringHelper;
import org.apache.lucene.util.Version;
/**
* Abstract class to do basic tests for fis format. NOTE: This test focuses on the fis impl, nothing
* else. The [stretch] goal is for this test to be so thorough in testing a new fis format that if
* this test passes, then all Lucene/Solr tests should also pass. Ie, if there is some bug in a
* given fis Format that this test fails to catch then this test needs to be improved!
*/
public abstract class BaseFieldInfoFormatTestCase extends BaseIndexFileFormatTestCase {
private static final IndexPackageAccess INDEX_PACKAGE_ACCESS =
TestSecrets.getIndexPackageAccess();
/** Test field infos read/write with a single field */
public void testOneField() throws Exception {
Directory dir = newDirectory();
Codec codec = getCodec();
SegmentInfo segmentInfo = newSegmentInfo(dir, "_123");
FieldInfo fi = createFieldInfo();
addAttributes(fi);
FieldInfos infos = INDEX_PACKAGE_ACCESS.newFieldInfosBuilder(null).add(fi).finish();
codec.fieldInfosFormat().write(dir, segmentInfo, "", infos, IOContext.DEFAULT);
FieldInfos infos2 = codec.fieldInfosFormat().read(dir, segmentInfo, "", IOContext.DEFAULT);
assertEquals(1, infos2.size());
assertNotNull(infos2.fieldInfo("field"));
assertTrue(infos2.fieldInfo("field").getIndexOptions() != IndexOptions.NONE);
assertFalse(infos2.fieldInfo("field").getDocValuesType() != DocValuesType.NONE);
assertFalse(infos2.fieldInfo("field").omitsNorms());
assertFalse(infos2.fieldInfo("field").hasPayloads());
assertFalse(infos2.fieldInfo("field").hasVectors());
assertEquals(0, infos2.fieldInfo("field").getPointDimensionCount());
assertEquals(0, infos2.fieldInfo("field").getVectorDimension());
assertFalse(infos2.fieldInfo("field").isSoftDeletesField());
dir.close();
}
/** Test field infos attributes coming back are not mutable */
public void testImmutableAttributes() throws Exception {
Directory dir = newDirectory();
Codec codec = getCodec();
SegmentInfo segmentInfo = newSegmentInfo(dir, "_123");
FieldInfo fi = createFieldInfo();
addAttributes(fi);
fi.putAttribute("foo", "bar");
fi.putAttribute("bar", "baz");
FieldInfos infos = INDEX_PACKAGE_ACCESS.newFieldInfosBuilder(null).add(fi).finish();
codec.fieldInfosFormat().write(dir, segmentInfo, "", infos, IOContext.DEFAULT);
FieldInfos infos2 = codec.fieldInfosFormat().read(dir, segmentInfo, "", IOContext.DEFAULT);
assertEquals(1, infos2.size());
assertNotNull(infos2.fieldInfo("field"));
Map<String, String> attributes = infos2.fieldInfo("field").attributes();
// shouldn't be able to modify attributes
expectThrows(
UnsupportedOperationException.class,
() -> {
attributes.put("bogus", "bogus");
});
dir.close();
}
/**
* Test field infos write that hits exception immediately on open. make sure we get our exception
* back, no file handle leaks, etc.
*/
public void testExceptionOnCreateOutput() throws Exception {
Failure fail =
new Failure() {
@Override
public void eval(MockDirectoryWrapper dir) throws IOException {
if (doFail && callStackContainsAnyOf("createOutput")) {
throw new FakeIOException();
}
}
};
MockDirectoryWrapper dir = newMockDirectory();
dir.failOn(fail);
Codec codec = getCodec();
SegmentInfo segmentInfo = newSegmentInfo(dir, "_123");
FieldInfo fi = createFieldInfo();
addAttributes(fi);
FieldInfos infos = INDEX_PACKAGE_ACCESS.newFieldInfosBuilder(null).add(fi).finish();
fail.setDoFail();
expectThrows(
FakeIOException.class,
() -> {
codec.fieldInfosFormat().write(dir, segmentInfo, "", infos, IOContext.DEFAULT);
});
fail.clearDoFail();
dir.close();
}
/**
* Test field infos write that hits exception on close. make sure we get our exception back, no
* file handle leaks, etc.
*/
public void testExceptionOnCloseOutput() throws Exception {
Failure fail =
new Failure() {
@Override
public void eval(MockDirectoryWrapper dir) throws IOException {
if (doFail && callStackContainsAnyOf("close")) {
throw new FakeIOException();
}
}
};
MockDirectoryWrapper dir = newMockDirectory();
dir.failOn(fail);
Codec codec = getCodec();
SegmentInfo segmentInfo = newSegmentInfo(dir, "_123");
FieldInfo fi = createFieldInfo();
addAttributes(fi);
FieldInfos infos = INDEX_PACKAGE_ACCESS.newFieldInfosBuilder(null).add(fi).finish();
fail.setDoFail();
expectThrows(
FakeIOException.class,
() -> {
codec.fieldInfosFormat().write(dir, segmentInfo, "", infos, IOContext.DEFAULT);
});
fail.clearDoFail();
dir.close();
}
/**
* Test field infos read that hits exception immediately on open. make sure we get our exception
* back, no file handle leaks, etc.
*/
public void testExceptionOnOpenInput() throws Exception {
Failure fail =
new Failure() {
@Override
public void eval(MockDirectoryWrapper dir) throws IOException {
if (doFail && callStackContainsAnyOf("openInput")) {
throw new FakeIOException();
}
}
};
MockDirectoryWrapper dir = newMockDirectory();
dir.failOn(fail);
Codec codec = getCodec();
SegmentInfo segmentInfo = newSegmentInfo(dir, "_123");
FieldInfo fi = createFieldInfo();
addAttributes(fi);
FieldInfos infos = INDEX_PACKAGE_ACCESS.newFieldInfosBuilder(null).add(fi).finish();
codec.fieldInfosFormat().write(dir, segmentInfo, "", infos, IOContext.DEFAULT);
fail.setDoFail();
expectThrows(
FakeIOException.class,
() -> {
codec.fieldInfosFormat().read(dir, segmentInfo, "", IOContext.DEFAULT);
});
fail.clearDoFail();
dir.close();
}
/**
* Test field infos read that hits exception on close. make sure we get our exception back, no
* file handle leaks, etc.
*/
public void testExceptionOnCloseInput() throws Exception {
Failure fail =
new Failure() {
@Override
public void eval(MockDirectoryWrapper dir) throws IOException {
if (doFail && callStackContainsAnyOf("close")) {
throw new FakeIOException();
}
}
};
MockDirectoryWrapper dir = newMockDirectory();
dir.failOn(fail);
Codec codec = getCodec();
SegmentInfo segmentInfo = newSegmentInfo(dir, "_123");
FieldInfo fi = createFieldInfo();
addAttributes(fi);
FieldInfos infos = INDEX_PACKAGE_ACCESS.newFieldInfosBuilder(null).add(fi).finish();
codec.fieldInfosFormat().write(dir, segmentInfo, "", infos, IOContext.DEFAULT);
fail.setDoFail();
expectThrows(
FakeIOException.class,
() -> {
codec.fieldInfosFormat().read(dir, segmentInfo, "", IOContext.DEFAULT);
});
fail.clearDoFail();
dir.close();
}
// TODO: more tests
/** Test field infos read/write with random fields, with different values. */
public void testRandom() throws Exception {
Directory dir = newDirectory();
Codec codec = getCodec();
SegmentInfo segmentInfo = newSegmentInfo(dir, "_123");
// generate a bunch of fields
int numFields = atLeast(2000);
Set<String> fieldNames = new HashSet<>();
for (int i = 0; i < numFields; i++) {
fieldNames.add(TestUtil.randomUnicodeString(random()));
}
String softDeletesField =
random().nextBoolean() ? TestUtil.randomUnicodeString(random()) : null;
var builder = INDEX_PACKAGE_ACCESS.newFieldInfosBuilder(softDeletesField);
for (String field : fieldNames) {
IndexableFieldType fieldType = randomFieldType(random());
boolean storeTermVectors = false;
boolean storePayloads = false;
boolean omitNorms = false;
if (fieldType.indexOptions() != IndexOptions.NONE) {
storeTermVectors = fieldType.storeTermVectors();
omitNorms = fieldType.omitNorms();
if (fieldType.indexOptions().compareTo(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS) >= 0) {
storePayloads = random().nextBoolean();
}
}
FieldInfo fi =
new FieldInfo(
field,
-1,
storeTermVectors,
omitNorms,
storePayloads,
fieldType.indexOptions(),
fieldType.docValuesType(),
-1,
new HashMap<>(),
fieldType.pointDimensionCount(),
fieldType.pointIndexDimensionCount(),
fieldType.pointNumBytes(),
fieldType.vectorDimension(),
fieldType.vectorEncoding(),
fieldType.vectorSimilarityFunction(),
field.equals(softDeletesField));
addAttributes(fi);
builder.add(fi);
}
FieldInfos infos = builder.finish();
codec.fieldInfosFormat().write(dir, segmentInfo, "", infos, IOContext.DEFAULT);
FieldInfos infos2 = codec.fieldInfosFormat().read(dir, segmentInfo, "", IOContext.DEFAULT);
assertEquals(infos, infos2);
dir.close();
}
private IndexableFieldType randomFieldType(Random r) {
FieldType type = new FieldType();
if (r.nextBoolean()) {
IndexOptions[] values = IndexOptions.values();
type.setIndexOptions(values[r.nextInt(values.length)]);
type.setOmitNorms(r.nextBoolean());
if (r.nextBoolean()) {
type.setStoreTermVectors(true);
if (type.indexOptions().compareTo(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS) >= 0) {
type.setStoreTermVectorPositions(r.nextBoolean());
type.setStoreTermVectorOffsets(r.nextBoolean());
if (type.storeTermVectorPositions()) {
type.setStoreTermVectorPayloads(r.nextBoolean());
}
}
}
}
if (r.nextBoolean()) {
DocValuesType values[] = DocValuesType.values();
type.setDocValuesType(values[r.nextInt(values.length)]);
}
if (r.nextBoolean()) {
int dimension = 1 + r.nextInt(PointValues.MAX_DIMENSIONS);
int indexDimension = 1 + r.nextInt(Math.min(dimension, PointValues.MAX_INDEX_DIMENSIONS));
int dimensionNumBytes = 1 + r.nextInt(PointValues.MAX_NUM_BYTES);
type.setDimensions(dimension, indexDimension, dimensionNumBytes);
}
if (r.nextBoolean()) {
int dimension = 1 + r.nextInt(VectorValues.MAX_DIMENSIONS);
VectorSimilarityFunction similarityFunction =
RandomPicks.randomFrom(r, VectorSimilarityFunction.values());
VectorEncoding encoding = RandomPicks.randomFrom(r, VectorEncoding.values());
type.setVectorAttributes(dimension, encoding, similarityFunction);
}
return type;
}
/** Hook to add any codec attributes to fieldinfo instances added in this test. */
protected void addAttributes(FieldInfo fi) {}
/** equality for entirety of fieldinfos */
protected void assertEquals(FieldInfos expected, FieldInfos actual) {
assertEquals(expected.size(), actual.size());
for (FieldInfo expectedField : expected) {
FieldInfo actualField = actual.fieldInfo(expectedField.number);
assertNotNull(actualField);
assertEquals(expectedField, actualField);
}
}
/** equality for two individual fieldinfo objects */
protected void assertEquals(FieldInfo expected, FieldInfo actual) {
assertEquals(expected.number, actual.number);
assertEquals(expected.name, actual.name);
assertEquals(expected.getDocValuesType(), actual.getDocValuesType());
assertEquals(expected.getIndexOptions(), actual.getIndexOptions());
assertEquals(expected.hasNorms(), actual.hasNorms());
assertEquals(expected.hasPayloads(), actual.hasPayloads());
assertEquals(expected.hasVectors(), actual.hasVectors());
assertEquals(expected.omitsNorms(), actual.omitsNorms());
assertEquals(expected.getDocValuesGen(), actual.getDocValuesGen());
}
/** Returns a new fake segment */
protected static SegmentInfo newSegmentInfo(Directory dir, String name) {
Version minVersion = random().nextBoolean() ? null : Version.LATEST;
return new SegmentInfo(
dir,
Version.LATEST,
minVersion,
name,
10000,
false,
Codec.getDefault(),
Collections.emptyMap(),
StringHelper.randomId(),
Collections.emptyMap(),
null);
}
@Override
protected void addRandomFields(Document doc) {
doc.add(new StoredField("foobar", TestUtil.randomSimpleString(random())));
}
private FieldInfo createFieldInfo() {
return new FieldInfo(
"field",
-1,
false,
false,
false,
TextField.TYPE_STORED.indexOptions(),
DocValuesType.NONE,
-1,
new HashMap<>(),
0,
0,
0,
0,
VectorEncoding.FLOAT32,
VectorSimilarityFunction.EUCLIDEAN,
false);
}
}