blob: e2c3c29c91e78b7c9dcdd71bcd46bae16986e2c7 [file] [log] [blame]
using J2N.Threading.Atomic;
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.TokenAttributes;
using Lucene.Net.Documents;
using Lucene.Net.Index.Extensions;
using NUnit.Framework;
using System;
using System.IO;
using Assert = Lucene.Net.TestFramework.Assert;
namespace Lucene.Net.Index
{
/*
* 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.
*/
using BytesRef = Lucene.Net.Util.BytesRef;
using Directory = Lucene.Net.Store.Directory;
using Document = Documents.Document;
using Field = Field;
using IndexInput = Lucene.Net.Store.IndexInput;
using IOContext = Lucene.Net.Store.IOContext;
using Lucene41PostingsFormat = Lucene.Net.Codecs.Lucene41.Lucene41PostingsFormat;
using LuceneTestCase = Lucene.Net.Util.LuceneTestCase;
using MockDirectoryWrapper = Lucene.Net.Store.MockDirectoryWrapper;
using RAMDirectory = Lucene.Net.Store.RAMDirectory;
using TestUtil = Lucene.Net.Util.TestUtil;
/// <summary>
/// this testcase tests whether multi-level skipping is being used
/// to reduce I/O while skipping through posting lists.
///
/// Skipping in general is already covered by several other
/// testcases.
///
/// </summary>
[TestFixture]
public class TestMultiLevelSkipList : LuceneTestCase
{
internal class CountingRAMDirectory : MockDirectoryWrapper
{
private readonly TestMultiLevelSkipList outerInstance;
public CountingRAMDirectory(TestMultiLevelSkipList outerInstance, Directory @delegate)
: base(Random, @delegate)
{
this.outerInstance = outerInstance;
}
public override IndexInput OpenInput(string fileName, IOContext context)
{
IndexInput @in = base.OpenInput(fileName, context);
if (fileName.EndsWith(".frq", StringComparison.Ordinal))
{
@in = new CountingStream(outerInstance, @in);
}
return @in;
}
}
[SetUp]
public override void SetUp()
{
base.SetUp();
counter = 0;
}
[Test]
public virtual void TestSimpleSkip()
{
Directory dir = new CountingRAMDirectory(this, new RAMDirectory());
IndexWriter writer = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new PayloadAnalyzer()).SetCodec(TestUtil.AlwaysPostingsFormat(new Lucene41PostingsFormat())).SetMergePolicy(NewLogMergePolicy()));
Term term = new Term("test", "a");
for (int i = 0; i < 5000; i++)
{
Document d1 = new Document();
d1.Add(NewTextField(term.Field, term.Text(), Field.Store.NO));
writer.AddDocument(d1);
}
writer.Commit();
writer.ForceMerge(1);
writer.Dispose();
AtomicReader reader = GetOnlySegmentReader(DirectoryReader.Open(dir));
for (int i = 0; i < 2; i++)
{
counter = 0;
DocsAndPositionsEnum tp = reader.GetTermPositionsEnum(term);
CheckSkipTo(tp, 14, 185); // no skips
CheckSkipTo(tp, 17, 190); // one skip on level 0
CheckSkipTo(tp, 287, 200); // one skip on level 1, two on level 0
// this test would fail if we had only one skip level,
// because than more bytes would be read from the freqStream
CheckSkipTo(tp, 4800, 250); // one skip on level 2
}
}
public virtual void CheckSkipTo(DocsAndPositionsEnum tp, int target, int maxCounter)
{
tp.Advance(target);
if (maxCounter < counter)
{
Assert.Fail("Too many bytes read: " + counter + " vs " + maxCounter);
}
Assert.AreEqual(target, tp.DocID, "Wrong document " + tp.DocID + " after skipTo target " + target);
Assert.AreEqual(1, tp.Freq, "Frequency is not 1: " + tp.Freq);
tp.NextPosition();
BytesRef b = tp.GetPayload();
Assert.AreEqual(1, b.Length);
Assert.AreEqual((sbyte)target, (sbyte)b.Bytes[b.Offset], "Wrong payload for the target " + target + ": " + (sbyte)b.Bytes[b.Offset]);
}
private class PayloadAnalyzer : Analyzer
{
internal readonly AtomicInt32 payloadCount = new AtomicInt32(-1);
protected internal override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
{
Tokenizer tokenizer = new MockTokenizer(reader, MockTokenizer.WHITESPACE, true);
return new TokenStreamComponents(tokenizer, new PayloadFilter(payloadCount, tokenizer));
}
}
private class PayloadFilter : TokenFilter
{
internal IPayloadAttribute payloadAtt;
internal AtomicInt32 payloadCount;
protected internal PayloadFilter(AtomicInt32 payloadCount, TokenStream input)
: base(input)
{
this.payloadCount = payloadCount;
payloadAtt = AddAttribute<IPayloadAttribute>();
}
public sealed override bool IncrementToken()
{
bool hasNext = m_input.IncrementToken();
if (hasNext)
{
payloadAtt.Payload = new BytesRef(new[] { (byte)payloadCount.IncrementAndGet() });
}
return hasNext;
}
}
private int counter = 0;
// Simply extends IndexInput in a way that we are able to count the number
// of bytes read
internal class CountingStream : IndexInput
{
private readonly TestMultiLevelSkipList outerInstance;
internal IndexInput input;
internal CountingStream(TestMultiLevelSkipList outerInstance, IndexInput input)
: base("CountingStream(" + input + ")")
{
this.outerInstance = outerInstance;
this.input = input;
}
public override byte ReadByte()
{
outerInstance.counter++;
return this.input.ReadByte();
}
public override void ReadBytes(byte[] b, int offset, int len)
{
outerInstance.counter += len;
this.input.ReadBytes(b, offset, len);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
this.input.Dispose();
}
}
public override long GetFilePointer()
{
return this.input.GetFilePointer();
}
public override void Seek(long pos)
{
this.input.Seek(pos);
}
public override long Length => this.input.Length;
public override object Clone()
{
return new CountingStream(outerInstance, (IndexInput)this.input.Clone());
}
}
}
}