| 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()); |
| } |
| } |
| } |
| } |